@probolabs/playwright 1.4.0-rc.3 → 1.4.0-rc.5
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/bin/probo.js +5 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli.js +82 -6
- package/dist/cli.js.map +1 -1
- package/dist/fixtures.cjs.map +1 -1
- package/dist/fixtures.js.map +1 -1
- package/dist/index.cjs +81 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +81 -6
- package/dist/index.js.map +1 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/codegen-api.d.ts.map +1 -1
- package/dist/types/test-suite-runner.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/fixtures.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixtures.cjs","sources":["../../probo-shared/dist/Interaction.js","../../probo-shared/dist/PlaywrightAction.js","../../probo-shared/dist/element-utils.js","../../probo-shared/dist/MessageType.js","../../probo-shared/dist/utils.js","../../probo-shared/dist/api-client.js","../../probo-shared/dist/AIModel.js","../../probo-shared/dist/chromium-flags.js","../src/fixtures.ts"],"sourcesContent":["export var ApplyAIStatus;\n(function (ApplyAIStatus) {\n ApplyAIStatus[\"PREPARE_START\"] = \"PREPARE_START\";\n ApplyAIStatus[\"PREPARE_SUCCESS\"] = \"PREPARE_SUCCESS\";\n ApplyAIStatus[\"PREPARE_ERROR\"] = \"PREPARE_ERROR\";\n ApplyAIStatus[\"SEND_START\"] = \"SEND_START\";\n ApplyAIStatus[\"SEND_SUCCESS\"] = \"SEND_SUCCESS\";\n ApplyAIStatus[\"SEND_ERROR\"] = \"SEND_ERROR\";\n ApplyAIStatus[\"APPLY_AI_ERROR\"] = \"APPLY_AI_ERROR\";\n ApplyAIStatus[\"APPLY_AI_CANCELLED\"] = \"APPLY_AI_CANCELLED\";\n ApplyAIStatus[\"SUMMARY_COMPLETED\"] = \"SUMMARY_COMPLETED\";\n ApplyAIStatus[\"SUMMARY_ERROR\"] = \"SUMMARY_ERROR\";\n})(ApplyAIStatus || (ApplyAIStatus = {}));\nexport var ReplayStatus;\n(function (ReplayStatus) {\n ReplayStatus[\"REPLAY_START\"] = \"REPLAY_START\";\n ReplayStatus[\"REPLAY_SUCCESS\"] = \"REPLAY_SUCCESS\";\n ReplayStatus[\"REPLAY_ERROR\"] = \"REPLAY_ERROR\";\n ReplayStatus[\"REPLAY_CANCELLED\"] = \"REPLAY_CANCELLED\";\n})(ReplayStatus || (ReplayStatus = {}));\n//# sourceMappingURL=Interaction.js.map","// Action constants\nexport var PlaywrightAction;\n(function (PlaywrightAction) {\n PlaywrightAction[\"VISIT_BASE_URL\"] = \"VISIT_BASE_URL\";\n PlaywrightAction[\"VISIT_URL\"] = \"VISIT_URL\";\n PlaywrightAction[\"CLICK\"] = \"CLICK\";\n PlaywrightAction[\"FILL_IN\"] = \"FILL_IN\";\n PlaywrightAction[\"SELECT_DROPDOWN\"] = \"SELECT_DROPDOWN\";\n PlaywrightAction[\"SELECT_MULTIPLE_DROPDOWN\"] = \"SELECT_MULTIPLE_DROPDOWN\";\n PlaywrightAction[\"CHECK_CHECKBOX\"] = \"CHECK_CHECKBOX\";\n PlaywrightAction[\"SELECT_RADIO\"] = \"SELECT_RADIO\";\n PlaywrightAction[\"TOGGLE_SWITCH\"] = \"TOGGLE_SWITCH\";\n PlaywrightAction[\"SET_SLIDER\"] = \"SET_SLIDER\";\n PlaywrightAction[\"TYPE_KEYS\"] = \"TYPE_KEYS\";\n PlaywrightAction[\"HOVER\"] = \"HOVER\";\n PlaywrightAction[\"ASSERT_EXACT_VALUE\"] = \"ASSERT_EXACT_VALUE\";\n PlaywrightAction[\"ASSERT_CONTAINS_VALUE\"] = \"ASSERT_CONTAINS_VALUE\";\n PlaywrightAction[\"ASSERT_URL\"] = \"ASSERT_URL\";\n PlaywrightAction[\"SCROLL_TO_ELEMENT\"] = \"SCROLL_TO_ELEMENT\";\n PlaywrightAction[\"EXTRACT_VALUE\"] = \"EXTRACT_VALUE\";\n PlaywrightAction[\"ASK_AI\"] = \"ASK_AI\";\n PlaywrightAction[\"EXECUTE_SCRIPT\"] = \"EXECUTE_SCRIPT\";\n PlaywrightAction[\"UPLOAD_FILES\"] = \"UPLOAD_FILES\";\n PlaywrightAction[\"WAIT_FOR\"] = \"WAIT_FOR\";\n PlaywrightAction[\"WAIT_FOR_OTP\"] = \"WAIT_FOR_OTP\";\n PlaywrightAction[\"GEN_TOTP\"] = \"GEN_TOTP\";\n // Sequence marker actions (not real interactions, used only for grouping)\n PlaywrightAction[\"SEQUENCE_START\"] = \"SEQUENCE_START\";\n PlaywrightAction[\"SEQUENCE_END\"] = \"SEQUENCE_END\";\n})(PlaywrightAction || (PlaywrightAction = {}));\n//# sourceMappingURL=PlaywrightAction.js.map","import { ElementTag } from \"./ElementTag.js\";\nimport { PlaywrightAction } from \"./PlaywrightAction.js\";\n/**\n * Resolves an action type to the corresponding ElementTag\n *\n * @param action The action type (CLICK, FILL_IN, SELECT_DROPDOWN)\n * @returns The corresponding ElementTag\n */\nexport function resolveElementTag(action) {\n switch (action) {\n case PlaywrightAction.CLICK:\n return [ElementTag.CLICKABLE, ElementTag.FILLABLE];\n case PlaywrightAction.FILL_IN:\n return [ElementTag.FILLABLE];\n case PlaywrightAction.SELECT_DROPDOWN:\n return [ElementTag.SELECTABLE];\n case PlaywrightAction.HOVER:\n return [ElementTag.CLICKABLE, ElementTag.FILLABLE, ElementTag.NON_INTERACTIVE_ELEMENT];\n case PlaywrightAction.ASSERT_EXACT_VALUE:\n case PlaywrightAction.ASSERT_CONTAINS_VALUE:\n case PlaywrightAction.EXTRACT_VALUE:\n case PlaywrightAction.WAIT_FOR:\n return [ElementTag.CLICKABLE, ElementTag.FILLABLE, ElementTag.NON_INTERACTIVE_ELEMENT];\n case PlaywrightAction.WAIT_FOR_OTP:\n case PlaywrightAction.GEN_TOTP:\n return [ElementTag.FILLABLE];\n default:\n console.error(`Unknown action: ${action}`);\n throw new Error(`Unknown action: ${action}`);\n }\n}\nexport function getElementText(element) {\n var _a;\n return element.textContent || element.innerText || element.value || ((_a = element.querySelector('input')) === null || _a === void 0 ? void 0 : _a.value) || '';\n}\n/**\n * Checks if any string in the array matches the given regular expression pattern.\n *\n * @param array - Array of strings to test.\n * @param pattern - Regular expression to test against each string.\n * @returns true if at least one string matches the pattern, false otherwise.\n */\nfunction testArray(array, pattern) {\n return array.some(item => pattern.test(item));\n}\n/**\n * Determines if an element is fillable (can accept text input)\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is fillable\n */\nexport function isFillableElement(element) {\n var _a;\n if (!element)\n return false;\n // Check if it's an input element\n if (element.tagName === 'INPUT') {\n const inputType = (_a = element.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();\n const isOTPInput = element.getAttribute('data-input-otp') === 'true' ||\n element.getAttribute('autocomplete') === 'one-time-code';\n // if it's an OTP input, don't consider it fillable as it's handled by OTP logic\n if (isOTPInput)\n return false;\n // Include text, password, email, number, tel, url, search, date, datetime-local, month, week, time\n const fillableTypes = [\n 'text', 'password', 'email', 'number', 'tel', 'url', 'search',\n 'date', 'datetime-local', 'month', 'week', 'time'\n ];\n return fillableTypes.includes(inputType);\n }\n // Check if it's a textarea\n if (element.tagName === 'TEXTAREA') {\n return true;\n }\n // Check if it's a contenteditable element\n if (element.getAttribute('contenteditable') === 'true') {\n return true;\n }\n return false;\n}\n/**\n * Determines if an element is a selectable dropdown element\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is a select element\n */\nexport function isSelectableElement(element) {\n return element && element.tagName === 'SELECT';\n}\n/**\n * Determines if an element is a non-interactive element (not clickable, fillable, or selectable)\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is non-interactive\n */\nexport function isSliderElement(element) {\n if (!element)\n return false;\n // check for <input type=\"range\">\n if (element.tagName === 'INPUT' && element.getAttribute('type') === 'range')\n return true;\n // check for role and additional attributes\n const sliderAttributes = ['aria-valuenow', 'aria-valuemin', 'aria-valuemax'];\n if (element.getAttribute('role') === 'slider' || sliderAttributes.some(attr => element.hasAttribute(attr)))\n return true;\n // check for class names\n const classNames = Array.from(element.classList);\n const sliderClassPattern = /^MuiSlider|mat-slider|mdl-slider|ui-slider|carousel/;\n if (testArray(classNames, sliderClassPattern))\n return true;\n return false;\n}\n/**\n * Determines if an element is an <input type=\"file\"> element.\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is an input file element\n */\nexport function isInputFileElement(element) {\n return element && element.tagName === 'INPUT' && element.getAttribute('type') === 'file';\n}\n/**\n * Determines if an element is clickable\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is fillable\n */\nexport function isClickableElement(element) {\n if (!element)\n return false;\n let depth = 0;\n while (depth < 5 && element && element.nodeType === Node.ELEMENT_NODE) {\n if (isClickableElementHelper(element) === IsClickable.YES)\n return true;\n if (isClickableElementHelper(element) === IsClickable.NO)\n return false;\n // if maybe, continue searching up to 5 levels up the DOM tree\n element = element.parentNode;\n depth++;\n }\n return false;\n}\n// clickable element detection result\nvar IsClickable;\n(function (IsClickable) {\n IsClickable[\"YES\"] = \"YES\";\n IsClickable[\"NO\"] = \"NO\";\n IsClickable[\"MAYBE\"] = \"MAYBE\";\n})(IsClickable || (IsClickable = {}));\nfunction isClickableElementHelper(element) {\n var _a, _b;\n if (!element)\n return IsClickable.NO;\n //check for tag name\n const tagName = element.tagName.toLowerCase();\n const clickableTags = [\n 'a', 'button',\n ];\n if (clickableTags.includes(tagName))\n return IsClickable.YES;\n //check for clickable <input>\n const inputType = (_a = element.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();\n const clickableTypes = [\n 'button', 'submit', 'reset', 'checkbox', 'radio',\n ];\n const ariaAutocompleteValues = [\n 'list', 'both',\n ];\n if (tagName === 'input') {\n if (clickableTypes.includes(inputType) || ariaAutocompleteValues.includes((_b = element.getAttribute('aria-autocomplete')) !== null && _b !== void 0 ? _b : ''))\n return IsClickable.YES;\n if (['date', 'number', 'range'].includes(inputType))\n return IsClickable.NO; //don't record the click as a change event will be generated for elements that generate an input change event\n }\n // check for cursor type\n const style = window.getComputedStyle(element);\n if (style.cursor === 'pointer')\n return IsClickable.YES;\n // check for attributes\n const clickableRoles = [\n 'button', 'combobox', 'listbox', 'dropdown', 'option', 'menu', 'menuitem',\n 'navigation', 'checkbox', 'switch', 'toggle', 'slider', 'textbox', 'listitem',\n 'presentation',\n ];\n const ariaPopupValues = [\n 'true', 'listbox', 'menu',\n ];\n if (element.hasAttribute('onclick') ||\n clickableRoles.includes(element.getAttribute('role') || '') ||\n ariaPopupValues.includes(element.getAttribute('aria-haspopup') || ''))\n return IsClickable.YES;\n // check for tabindex (means element is focusable and therefore clickable)\n if (parseInt(element.getAttribute('tabindex') || '-1') >= 0)\n return IsClickable.YES;\n // extract class names\n const classNames = Array.from(element.classList);\n // check for checkbox/radio-like class name - TODO: check if can be removed\n const checkboxPattern = /checkbox|switch|toggle|slider/i;\n if (testArray(classNames, checkboxPattern))\n return IsClickable.YES;\n // check for Material UI class names\n const muiClickableClassPattern = /MuiButton|MuiIconButton|MuiChip|MuiMenuItem|MuiListItem|MuiInputBase|MuiOutlinedInput|MuiSelect|MuiAutocomplete|MuiToggleButton|MuiBackdrop-root|MuiBackdrop-invisible/;\n if (testArray(classNames, muiClickableClassPattern))\n return IsClickable.YES;\n // check for SalesForce class names\n const sfClassPattern = /slds-button|slds-dropdown|slds-combobox|slds-picklist|slds-tabs|slds-pill|slds-action|slds-row-action|slds-context-bar|slds-input|slds-rich-text-area|slds-radio|slds-checkbox|slds-toggle|slds-link|slds-accordion|slds-tree/;\n if (testArray(classNames, sfClassPattern))\n return IsClickable.YES;\n // check for chart dots\n const chartClickableClassPattern = /recharts-dot/;\n if (testArray(classNames, chartClickableClassPattern))\n return IsClickable.YES;\n // check for React component classes\n const reactClickableClassPattern = /react-select|ant-select|rc-select|react-dropdown|react-autocomplete|react-datepicker|react-modal|react-tooltip|react-popover|react-menu|react-tabs|react-accordion|react-collapse|react-toggle|react-switch|react-checkbox|react-radio|react-button|react-link|react-card|react-list-item|react-menu-item|react-option|react-tab|react-panel|react-drawer|react-sidebar|react-nav|react-breadcrumb|react-pagination|react-stepper|react-wizard|react-carousel|react-slider|react-range|react-progress|react-badge|react-chip|react-tag|react-avatar|react-icon|react-fab|react-speed-dial|react-floating|react-sticky|react-affix|react-backdrop|react-overlay|react-portal|react-transition|react-animate|react-spring|react-framer|react-gesture|react-drag|react-drop|react-sortable|react-resizable|react-split|react-grid|react-table|react-datagrid|react-tree|react-treeview|react-file|react-upload|react-cropper|react-image|react-gallery|react-lightbox|react-player|react-video|react-audio|react-chart|react-graph|react-diagram|react-flow|react-d3|react-plotly|react-vega|react-vis|react-nivo|react-recharts|react-victory|react-echarts|react-highcharts|react-google-charts|react-fusioncharts|react-apexcharts|react-chartjs|react-chartkick|react-sparklines|react-trend|react-smooth|react-animated|react-lottie|react-spring|react-framer-motion|react-pose|react-motion|react-transition-group|react-router|react-navigation/i;\n if (testArray(classNames, reactClickableClassPattern))\n return IsClickable.YES;\n //check for cloudinary class names\n const cloudinaryClickableClassPattern = /cld-combobox|cld-upload-button|cld-controls|cld-player|cld-tab|cld-menu-item|cld-close|cld-play|cld-pause|cld-fullscreen|cld-browse|cld-cancel|cld-retry/;\n if (testArray(classNames, cloudinaryClickableClassPattern))\n return IsClickable.YES;\n return IsClickable.MAYBE;\n}\nexport function isTextInputElement(element) {\n return (element.tagName === 'INPUT' && element.getAttribute('type') !== 'checkbox' && element.getAttribute('type') !== 'radio') ||\n element.tagName === 'TEXTAREA' ||\n element.getAttribute('contenteditable') === 'true' ||\n element.getAttribute('role') === 'textbox';\n}\nexport function getParentNode(element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE)\n return null;\n let parent = null;\n // SF is using slots and shadow DOM heavily\n // However, there might be slots in the light DOM which shouldn't be traversed\n if (element.assignedSlot && element.getRootNode() instanceof ShadowRoot)\n parent = element.assignedSlot;\n else\n parent = element.parentNode;\n // Check if we're at a shadow root\n if (parent && parent.nodeType !== Node.ELEMENT_NODE && parent.getRootNode() instanceof ShadowRoot)\n parent = parent.getRootNode().host;\n return parent;\n}\nexport function getElementDepth(element) {\n let depth = 0;\n let currentElement = element;\n while ((currentElement === null || currentElement === void 0 ? void 0 : currentElement.nodeType) === Node.ELEMENT_NODE) {\n depth++;\n currentElement = getParentNode(currentElement);\n }\n return depth;\n}\n//# sourceMappingURL=element-utils.js.map","// WebSocketsMessageType enum for WebSocket and event message types shared across the app\nexport var WebSocketsMessageType;\n(function (WebSocketsMessageType) {\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_PREPARE_START\"] = \"INTERACTION_APPLY_AI_PREPARE_START\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_PREPARE_SUCCESS\"] = \"INTERACTION_APPLY_AI_PREPARE_SUCCESS\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_PREPARE_ERROR\"] = \"INTERACTION_APPLY_AI_PREPARE_ERROR\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SEND_TO_LLM_START\"] = \"INTERACTION_APPLY_AI_SEND_TO_LLM_START\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SEND_TO_LLM_SUCCESS\"] = \"INTERACTION_APPLY_AI_SEND_TO_LLM_SUCCESS\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SEND_TO_LLM_ERROR\"] = \"INTERACTION_APPLY_AI_SEND_TO_LLM_ERROR\";\n WebSocketsMessageType[\"INTERACTION_REPLAY_START\"] = \"INTERACTION_REPLAY_START\";\n WebSocketsMessageType[\"INTERACTION_REPLAY_SUCCESS\"] = \"INTERACTION_REPLAY_SUCCESS\";\n WebSocketsMessageType[\"INTERACTION_REPLAY_ERROR\"] = \"INTERACTION_REPLAY_ERROR\";\n WebSocketsMessageType[\"INTERACTION_REPLAY_CANCELLED\"] = \"INTERACTION_REPLAY_CANCELLED\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_CANCELLED\"] = \"INTERACTION_APPLY_AI_CANCELLED\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_ERROR\"] = \"INTERACTION_APPLY_AI_ERROR\";\n WebSocketsMessageType[\"INTERACTION_STEP_CREATED\"] = \"INTERACTION_STEP_CREATED\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SUMMARY_COMPLETED\"] = \"INTERACTION_APPLY_AI_SUMMARY_COMPLETED\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SUMMARY_ERROR\"] = \"INTERACTION_APPLY_AI_SUMMARY_ERROR\";\n WebSocketsMessageType[\"OTP_RETRIEVED\"] = \"OTP_RETRIEVED\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_START\"] = \"TEST_SUITE_RUN_START\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_LOG\"] = \"TEST_SUITE_RUN_LOG\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_COMPLETE\"] = \"TEST_SUITE_RUN_COMPLETE\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_ERROR\"] = \"TEST_SUITE_RUN_ERROR\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_REPORTER_EVENT\"] = \"TEST_SUITE_RUN_REPORTER_EVENT\";\n})(WebSocketsMessageType || (WebSocketsMessageType = {}));\n//# sourceMappingURL=MessageType.js.map","import { PlaywrightAction } from './PlaywrightAction.js';\n/**\n * Logging levels for Probo\n */\nexport var ProboLogLevel;\n(function (ProboLogLevel) {\n ProboLogLevel[\"DEBUG\"] = \"DEBUG\";\n ProboLogLevel[\"INFO\"] = \"INFO\";\n ProboLogLevel[\"LOG\"] = \"LOG\";\n ProboLogLevel[\"WARN\"] = \"WARN\";\n ProboLogLevel[\"ERROR\"] = \"ERROR\";\n})(ProboLogLevel || (ProboLogLevel = {}));\nconst logLevelOrder = {\n [ProboLogLevel.DEBUG]: 0,\n [ProboLogLevel.INFO]: 1,\n [ProboLogLevel.LOG]: 2,\n [ProboLogLevel.WARN]: 3,\n [ProboLogLevel.ERROR]: 4,\n};\nexport class ProboLogger {\n constructor(prefix, level = ProboLogLevel.INFO) {\n this.prefix = prefix;\n this.level = level;\n }\n setLogLevel(level) {\n console.log(`[${this.prefix}] Setting log level to: ${level} (was: ${this.level})`);\n this.level = level;\n }\n shouldLog(level) {\n return logLevelOrder[level] >= logLevelOrder[this.level];\n }\n preamble(level) {\n const now = new Date();\n const hours = String(now.getHours()).padStart(2, '0');\n const minutes = String(now.getMinutes()).padStart(2, '0');\n const seconds = String(now.getSeconds()).padStart(2, '0');\n const milliseconds = String(now.getMilliseconds()).padStart(3, '0');\n return `[${hours}:${minutes}:${seconds}.${milliseconds}] [${this.prefix}] [${level}]`;\n }\n debug(...args) { if (this.shouldLog(ProboLogLevel.DEBUG))\n console.debug(this.preamble(ProboLogLevel.DEBUG), ...args); }\n info(...args) { if (this.shouldLog(ProboLogLevel.INFO))\n console.info(this.preamble(ProboLogLevel.INFO), ...args); }\n log(...args) { if (this.shouldLog(ProboLogLevel.LOG))\n console.log(this.preamble(ProboLogLevel.LOG), ...args); }\n warn(...args) { if (this.shouldLog(ProboLogLevel.WARN))\n console.warn(this.preamble(ProboLogLevel.WARN), ...args); }\n error(...args) { if (this.shouldLog(ProboLogLevel.ERROR))\n console.error(this.preamble(ProboLogLevel.ERROR), ...args); }\n}\n// Element cleaner logging\n// const elementLogger = new ProboLogger('element-cleaner');\n/**\n * Cleans and returns a minimal element info structure.\n */\n//TODO: is this needed?\n/* export function cleanupElementInfo(elementInfo: ElementInfo): CleanElementInfo {\n elementLogger.debug(\n `Cleaning up element info for ${elementInfo.tag} at index ${elementInfo.index}`\n );\n const depth = elementInfo.depth ?? elementInfo.getDepth();\n const cleanEl = {\n index: elementInfo.index,\n tag: elementInfo.tag,\n type: elementInfo.type,\n text: elementInfo.text,\n html: elementInfo.html,\n xpath: elementInfo.xpath,\n css_selector: elementInfo.css_selector,\n iframe_selector: elementInfo.iframe_selector,\n bounding_box: elementInfo.bounding_box,\n depth\n };\n elementLogger.debug(`Cleaned element: ${JSON.stringify(cleanEl)}`);\n return cleanEl;\n} */\n/**\n * Cleans highlighted elements in an instruction payload.\n */\n/* export function cleanupInstructionElements(instruction: any): any {\n if (!instruction?.result?.highlighted_elements) {\n elementLogger.debug('No highlighted elements to clean');\n return instruction;\n }\n elementLogger.debug(\n `Cleaning ${instruction.result.highlighted_elements.length} highlighted elements`\n );\n const cleaned = {\n ...instruction,\n result: {\n ...instruction.result,\n highlighted_elements: instruction.result.highlighted_elements.map(\n (el: ElementInfo) => cleanupElementInfo(el)\n )\n }\n };\n elementLogger.debug('Instruction cleaning completed');\n return cleaned;\n} */\n// Determine whether an interaction can return a value\nexport const hasReturnValue = (i) => {\n return [\n PlaywrightAction.EXTRACT_VALUE,\n PlaywrightAction.ASK_AI,\n PlaywrightAction.EXECUTE_SCRIPT\n ].includes(i.action);\n};\nexport const getReturnValueParameterName = (i) => {\n switch (i.action) {\n case PlaywrightAction.EXTRACT_VALUE:\n case PlaywrightAction.EXECUTE_SCRIPT:\n return i.parameterName;\n case PlaywrightAction.ASK_AI:\n return i.parameterName.replace(/^assert_/, '');\n default:\n console.error(`Action ${i.action} has no return value`);\n return '';\n }\n};\n// Determine whether an interaction can be parameterized\nexport const isParameterizable = (i) => {\n const parameterizableActions = [\n PlaywrightAction.FILL_IN,\n PlaywrightAction.SELECT_DROPDOWN,\n PlaywrightAction.SET_SLIDER,\n PlaywrightAction.ASSERT_CONTAINS_VALUE,\n PlaywrightAction.ASSERT_EXACT_VALUE,\n PlaywrightAction.VISIT_URL,\n PlaywrightAction.ASSERT_URL,\n PlaywrightAction.UPLOAD_FILES,\n PlaywrightAction.WAIT_FOR,\n PlaywrightAction.GEN_TOTP,\n PlaywrightAction.WAIT_FOR_OTP\n ];\n return parameterizableActions.includes(i.action) || (i.action === PlaywrightAction.ASK_AI && i.argument);\n};\n// Determine whether an interaction is AI-related\nexport const isAI = (i) => {\n var _a, _b, _c, _d, _e, _f;\n return !['TYPE_KEYS', 'VISIT_URL', 'EXECUTE_SCRIPT'].includes(i.action) &&\n (i.action === PlaywrightAction.ASK_AI ||\n (((_b = (_a = i.serverResponse) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.prompt) && (((_d = (_c = i.serverResponse) === null || _c === void 0 ? void 0 : _c.result) === null || _d === void 0 ? void 0 : _d.error) === \"\" || !((_f = (_e = i.serverResponse) === null || _e === void 0 ? void 0 : _e.result) === null || _f === void 0 ? void 0 : _f.error))));\n};\n// Determine whether an interaction is fortified (doesn't need AI processing)\nexport const isFortifiedInteraction = (i) => {\n return [\n PlaywrightAction.VISIT_URL,\n PlaywrightAction.ASSERT_URL,\n PlaywrightAction.TYPE_KEYS,\n PlaywrightAction.EXECUTE_SCRIPT,\n PlaywrightAction.ASK_AI\n ].includes(i.action);\n};\n/**\n * Check if an interaction is a sequence marker (SEQUENCE_START or SEQUENCE_END)\n * @param action - The PlaywrightAction to check\n * @returns true if the action is a sequence marker\n */\nexport function isSequenceMarker(action) {\n return action === PlaywrightAction.SEQUENCE_START || action === PlaywrightAction.SEQUENCE_END;\n}\nexport function singleQuoteString(str) {\n if (!str)\n return '';\n return `'${str.replace(/'/g, \"\\\\'\")}'`;\n}\nexport function doubleQuoteString(str) {\n if (!str)\n return '';\n return `\"${str.replace(/\"/g, '\\\\\"')}\"`;\n}\n/**\n * Converts a string to a filesystem-safe slug.\n * Used for filenames/package names (not URL parsing).\n */\nexport function slugify(text) {\n if (!text)\n return 'scenario';\n return text\n .toLowerCase()\n .trim()\n .replace(/[^a-zA-Z0-9-_]/g, '-') // Replace non-alphanumeric chars with hyphens\n .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen\n .replace(/^-|-$/g, '') // Remove leading/trailing hyphens\n .substring(0, 100); // Limit length to 100 chars\n}\n// Normalize a parameter name to a legal JS identifier\nexport function normalizeParameterName(name) {\n // 0. Remove leading/trailing spaces\n // 1. Replace invalid characters with underscores\n // 2. collapse multiple underscores\n // 3. Remove trailing underscores\n // 4. If starts with a digit, prepend '_'\n // 5. Convert to lowercase\n name = name.trim();\n name = name.replace(/[^a-zA-Z0-9_$]/g, '_');\n name = name.replace(/_+/g, '_');\n name = name.replace(/_+$/, ''); // Remove trailing underscores\n if (/^[0-9]/.test(name)) {\n name = '_' + name;\n }\n name = name.toLowerCase();\n // Ensure we never return an empty string or just underscores\n if (!name || name === '_' || /^_+$/.test(name)) {\n name = 'param_' + Date.now();\n }\n return name;\n}\nexport function matchRegex(str, regex) {\n //parse the regex string\n const match = regex.match(/^\\/(.+)\\/([gimsuy]*)$/);\n if (!match) // normal string\n return str.includes(regex);\n else { // regex string\n const pattern = match[1].replace(/\\\\/g, '\\\\');\n const flags = match[2];\n console.log(`Matching ${str} against ${pattern} with flags ${flags}`);\n return new RegExp(pattern, flags).test(str);\n }\n}\n/**\n * Truncates a URL by replacing query parameters (everything after ?) with three dots.\n * @param url The URL to truncate\n * @returns The truncated URL with three dots replacing query parameters\n */\nexport function truncateUrl(url) {\n if (!url)\n return '';\n const questionMarkIndex = url.indexOf('?');\n if (questionMarkIndex === -1) {\n // No query parameters found, return the original URL\n return url;\n }\n const baseUrl = url.substring(0, questionMarkIndex);\n return `${baseUrl}...`;\n}\n/**\n * Sets up browser console logging on a Playwright page.\n * This function removes all existing console listeners and optionally adds a new one.\n * @param page The Playwright page instance\n * @param enableConsoleLogs Whether to enable console logging\n * @param logger Optional logger instance to use for output (defaults to console.log)\n */\nexport const setupBrowserConsoleLogs = (page, enableConsoleLogs, logger = null) => {\n // Always remove all existing console listeners first\n page.removeAllListeners('console');\n // Always add a listener, but filter output based on enableConsoleLogs\n const listener = (msg) => {\n if (enableConsoleLogs) {\n const type = msg.type();\n const text = msg.text();\n if (logger) {\n logger.log(`[Browser-${type}]: ${text}`);\n }\n else {\n console.log(`[Browser-${type}]: ${text}`);\n }\n }\n // If disabled, do nothing (silently ignore the console message)\n };\n page.on('console', listener);\n};\n/**\n * Safely interpolates template literals in a string using the provided context.\n * Similar to JavaScript template literals, but executed safely at runtime.\n * Recursively interpolates until no more template literals remain.\n *\n * This function only interpolates the argument string itself, not the entire context.\n * When a template literal resolves to another string containing template literals,\n * it recursively interpolates that result.\n *\n * @param str The string containing template literal syntax (e.g., \"Hello ${name}\")\n * @param context The context object containing variables for interpolation\n * @param maxDepth Maximum recursion depth to prevent infinite loops (default: 10)\n * @returns The interpolated string, or the original string if no interpolation is needed\n */\nexport function interpolateTemplate(str, context, maxDepth = 10) {\n if (typeof str !== 'string' || !str.includes('${')) {\n return str;\n }\n if (maxDepth <= 0) {\n console.warn('⚠️ Maximum interpolation depth reached, returning partially interpolated string');\n return str;\n }\n try {\n // Escape backticks in the template to prevent template literal injection\n const escapedTemplate = str.replace(/\\\\/g, '\\\\\\\\').replace(/`/g, '\\\\`');\n // Create a safe template execution function\n // Assign all context properties to variables so they're accessible in the template\n // This works for both simple values and nested objects like process.env\n const contextKeys = Object.keys(context);\n const assignments = contextKeys.map(key => `const ${key} = ctx.${key};`).join('\\n');\n const functionBody = `\n const ctx = arguments[0];\n ${assignments}\n return \\`${escapedTemplate}\\`;\n `;\n const compiled = new Function(functionBody);\n const rendered = compiled(context);\n // If the result still contains template literals, recursively interpolate\n if (rendered.includes('${') && rendered !== str) {\n return interpolateTemplate(rendered, context, maxDepth - 1);\n }\n return rendered;\n }\n catch (e) {\n const error = e instanceof Error ? e : new Error(String(e));\n console.error('⚠️ Template evaluation failed:', error);\n throw error;\n }\n}\nexport class Mutex {\n constructor() {\n this._locked = false;\n this._waiters = [];\n }\n lock() {\n return new Promise((resolve) => {\n const unlock = () => {\n if (this._waiters.length > 0) {\n const nextResolve = this._waiters.shift();\n if (nextResolve) {\n nextResolve(unlock);\n }\n }\n else {\n this._locked = false;\n }\n };\n if (this._locked) {\n this._waiters.push(resolve);\n }\n else {\n this._locked = true;\n resolve(unlock);\n }\n });\n }\n async withLock(fn) {\n const unlock = await this.lock();\n try {\n return await fn();\n }\n finally {\n unlock();\n }\n }\n}\n//# sourceMappingURL=utils.js.map","import pRetry from 'p-retry';\nimport { ProboLogger } from './utils.js';\nexport const apiLogger = new ProboLogger('apiclient');\nexport class ApiError extends Error {\n constructor(status, message, data) {\n super(message);\n this.status = status;\n this.data = data;\n this.name = 'ApiError';\n // Remove stack trace for cleaner error messages\n this.stack = undefined;\n }\n toString() {\n return `${this.message} (Status: ${this.status})`;\n }\n}\nexport class ApiClient {\n constructor(apiUrl, token, maxRetries = 3, initialBackoff = 1000) {\n this.apiUrl = apiUrl;\n this.token = token;\n this.maxRetries = maxRetries;\n this.initialBackoff = initialBackoff;\n }\n /**\n * Determines if an error should be retried.\n * Only retries on timeout and network errors, not on client/server errors.\n */\n isRetryableError(error) {\n var _a, _b, _c, _d, _e;\n // Network/connection errors should be retried\n if ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('fetch failed'))\n return true;\n if ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('network'))\n return true;\n if ((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('ETIMEDOUT'))\n return true;\n if ((_d = error.message) === null || _d === void 0 ? void 0 : _d.includes('ECONNRESET'))\n return true;\n if ((_e = error.message) === null || _e === void 0 ? void 0 : _e.includes('ECONNREFUSED'))\n return true;\n if (error.code === 'ETIMEDOUT')\n return true;\n if (error.code === 'ECONNRESET')\n return true;\n if (error.code === 'ECONNREFUSED')\n return true;\n // If it's an ApiError, check the status code\n if (error instanceof ApiError) {\n // Retry on timeout-related status codes\n if (error.status === 408)\n return true; // Request Timeout\n if (error.status === 502)\n return true; // Bad Gateway (temporary)\n if (error.status === 503)\n return true; // Service Unavailable (temporary)\n if (error.status === 504)\n return true; // Gateway Timeout\n if (error.status === 0)\n return true; // Network error\n // Don't retry on client errors (4xx) or server errors (5xx)\n // These indicate problems that won't be fixed by retrying\n return false;\n }\n // For unknown errors, don't retry to avoid masking issues\n return false;\n }\n /**\n * Generic helper to wrap API requests with retry logic and consistent error handling.\n */\n async requestWithRetry(operationName, operation) {\n return pRetry(operation, {\n retries: this.maxRetries,\n minTimeout: this.initialBackoff,\n shouldRetry: (error) => {\n const shouldRetry = this.isRetryableError(error);\n if (!shouldRetry) {\n apiLogger.error(`${operationName} failed with non-retryable error: ${error.message || error}`);\n }\n return shouldRetry;\n },\n onFailedAttempt: error => {\n apiLogger.warn(`${operationName} failed (retryable), attempt ${error.attemptNumber} of ${error.retriesLeft + error.attemptNumber}. Error: ${error.message}`);\n }\n });\n }\n async handleResponse(response) {\n var _a;\n try {\n const data = await response.json();\n const error = `${(data === null || data === void 0 ? void 0 : data.error) || 'Unknown error'}`;\n apiLogger.debug(`API response: ${JSON.stringify(data)}`);\n if (!response.ok) {\n switch (response.status) {\n case 401:\n throw new ApiError(401, `Unauthorized - Invalid or missing authentication token: ${error}`);\n case 403:\n throw new ApiError(403, `Forbidden - You do not have permission to perform this action: ${error}`);\n case 400:\n throw new ApiError(400, `Bad Request: ${error}`);\n case 404:\n throw new ApiError(404, `Not Found: ${error}`);\n case 500:\n throw new ApiError(500, `Internal Server Error: ${error}`);\n default:\n throw new ApiError(response.status, `API Error: ${error}`);\n }\n }\n return data;\n }\n catch (error) {\n // Only throw the original error if it's not a network error\n if (!((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('fetch failed'))) {\n throw error;\n }\n throw new ApiError(0, 'Network error: fetch failed');\n }\n }\n getHeaders() {\n const headers = {\n 'Content-Type': 'application/json',\n };\n // Always include token in headers now that we have a default\n headers['Authorization'] = `Token ${this.token}`;\n return headers;\n }\n async createStep(options) {\n apiLogger.debug('creating step ', options.stepPrompt);\n return this.requestWithRetry('createStep', async () => {\n const response = await fetch(`${this.apiUrl}/step-runners/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n step_id: options.stepIdFromServer,\n scenario_name: options.scenarioName,\n step_prompt: options.stepPrompt,\n argument: options.argument,\n initial_screenshot: options.initial_screenshot_url,\n initial_html_content: options.initial_html_content,\n use_cache: options.use_cache,\n url: options.url,\n action: options.action,\n vanilla_prompt: options.vanilla_prompt,\n is_vanilla_prompt_robust: options.is_vanilla_prompt_robust,\n target_element_name: options.target_element_name,\n position: options.position\n }),\n });\n const data = await this.handleResponse(response);\n return data.step.id;\n });\n }\n async patchStep(stepId, fields) {\n // Use PATCH /steps/:id/ endpoint with partial=true\n apiLogger.debug(`patching step #${stepId} with fields:`, Object.keys(fields));\n return this.requestWithRetry('patchStep', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/`, {\n method: 'PATCH',\n headers: this.getHeaders(),\n body: JSON.stringify(fields)\n });\n const data = await this.handleResponse(response);\n return;\n });\n }\n /* async resolveNextInstruction(stepId: string, instruction: Instruction | null, aiModel?: string) {\n apiLogger.debug(`resolving next instruction: ${instruction}`);\n return this.requestWithRetry('resolveNextInstruction', async () => {\n apiLogger.debug(`API client: Resolving next instruction for step ${stepId}`);\n \n const cleanInstruction = cleanupInstructionElements(instruction);\n \n const response = await fetch(`${this.apiUrl}/step-runners/${stepId}/run/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n executed_instruction: cleanInstruction,\n ai_model: aiModel\n }),\n });\n \n const data = await this.handleResponse(response);\n return data.instruction;\n });\n } */\n async uploadScreenshot(screenshot_bytes) {\n return this.requestWithRetry('uploadScreenshot', async () => {\n const response = await fetch(`${this.apiUrl}/upload-screenshots/`, {\n method: 'POST',\n headers: {\n 'Authorization': `Token ${this.token}`\n },\n body: screenshot_bytes,\n });\n const data = await this.handleResponse(response);\n return data.screenshot_url;\n });\n }\n async findStepById(stepId) {\n apiLogger.debug(`Finding step by id: ${stepId}`);\n return this.requestWithRetry('findStepById', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/`, {\n method: 'GET',\n headers: this.getHeaders(),\n });\n const data = await this.handleResponse(response);\n return data.step;\n });\n }\n async findStepByPrompt(prompt, scenarioName, url = '') {\n apiLogger.debug(`Finding step by prompt: ${prompt} and scenario: ${scenarioName}`);\n return this.requestWithRetry('findStepByPrompt', async () => {\n const response = await fetch(`${this.apiUrl}/step-runners/find-step-by-prompt/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n prompt: prompt,\n scenario_name: scenarioName,\n url: url\n }),\n });\n try {\n const data = await this.handleResponse(response);\n return {\n step: data.step,\n total_count: data.total_count\n };\n }\n catch (error) {\n // If we get a 404, the step doesn't exist\n if (error instanceof ApiError && error.status === 404) {\n return null;\n }\n // For any other error, rethrow\n throw error;\n }\n });\n }\n async resetStep(stepId) {\n return this.requestWithRetry('resetStep', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/reset/`, {\n method: 'POST',\n headers: this.getHeaders(),\n });\n const data = await this.handleResponse(response);\n return data;\n });\n }\n async interactionToStep(scenarioId, interaction, position = -1) {\n // Use POST /interaction-to-step/ endpoint\n // Backend will create new step or update existing one based on interaction_id\n apiLogger.debug(`converting interaction #${interaction.interactionId} to step`);\n return this.requestWithRetry('interactionToStep', async () => {\n var _a, _b, _c, _d;\n const response = await fetch(`${this.apiUrl}/interaction-to-step/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n scenario_id: scenarioId,\n position,\n interaction_id: interaction.interactionId,\n action: interaction.action,\n argument: interaction.argument,\n element_css_selector: ((_a = interaction.elementInfo) === null || _a === void 0 ? void 0 : _a.css_selector) || '',\n iframe_selector: ((_b = interaction.elementInfo) === null || _b === void 0 ? void 0 : _b.iframe_selector) || '',\n smart_selector: ((_c = interaction.elementInfo) === null || _c === void 0 ? void 0 : _c.smart_selector) || null,\n smart_iframe_selector: ((_d = interaction.elementInfo) === null || _d === void 0 ? void 0 : _d.smart_iframe_selector) || null,\n prompt: interaction.nativeDescription,\n vanilla_prompt: interaction.nativeDescription,\n is_vanilla_prompt_robust: interaction.isNativeDescriptionElaborate || false,\n target_element_name: interaction.nativeName,\n target_element_info: interaction.elementInfo,\n url: interaction.url,\n is_secret: interaction.isSecret || false,\n })\n });\n const data = await this.handleResponse(response);\n return [data.result.step_id, data.result.matched_step, data.scenario_id];\n });\n }\n async actionToPrompt(action2promptInput, aiModel) {\n // Use POST /action-to-prompt/ endpoint\n apiLogger.debug(`running action2prompt for step #${action2promptInput.step_id}`);\n return this.requestWithRetry('actionToPrompt', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${action2promptInput.step_id}/action_to_prompt/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ ...action2promptInput, 'model': aiModel })\n });\n const data = await this.handleResponse(response);\n return data;\n });\n }\n async summarizeScenario(scenarioId, aiModel) {\n return this.requestWithRetry('summarizeScenario', async () => {\n const response = await fetch(`${this.apiUrl}/api/scenarios/${scenarioId}/summary`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ 'model': aiModel })\n });\n const data = await this.handleResponse(response);\n return data;\n });\n }\n async askAI(question, scenarioName, screenshot, aiModel) {\n apiLogger.debug(`Asking AI question: \"${question}\", scenarioName: ${scenarioName}`);\n apiLogger.debug(`headers: ${JSON.stringify(this.getHeaders())}`);\n return this.requestWithRetry('askAI', async () => {\n const response = await fetch(`${this.apiUrl}/api/ask-ai/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n question: question,\n scenario_name: scenarioName,\n screenshot: screenshot,\n model: aiModel\n }),\n });\n const data = await this.handleResponse(response);\n return data;\n });\n }\n async screenshotReasoning(stepId, prompt, aiModel) {\n apiLogger.debug(`Performing screenshot reasoning for step: ${stepId}`);\n return this.requestWithRetry('screenshotReasoning', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/screenshot_reasoning/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n prompt: prompt,\n model: aiModel\n }),\n });\n const data = await this.handleResponse(response);\n return data.action;\n });\n }\n async findBestCandidateElement(stepId, candidatesScreenshotUrl, candidateElements, aiModel) {\n apiLogger.debug(`Finding best candidate element for step: ${stepId}`);\n return this.requestWithRetry('findBestCandidateElement', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/find_best_candidate_element/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n screenshot_url: candidatesScreenshotUrl,\n candidate_elements: candidateElements,\n model: aiModel\n }),\n });\n const data = await this.handleResponse(response);\n return data.index;\n });\n }\n} /* ApiClient */\n//# sourceMappingURL=api-client.js.map","/**\n * Available AI models for LLM operations\n */\nexport var AIModel;\n(function (AIModel) {\n AIModel[\"AZURE_GPT4\"] = \"azure-gpt4\";\n AIModel[\"AZURE_GPT4_MINI\"] = \"azure-gpt4-mini\";\n AIModel[\"GEMINI_1_5_FLASH\"] = \"gemini-1.5-flash\";\n AIModel[\"GEMINI_2_5_FLASH\"] = \"gemini-2.5-flash\";\n AIModel[\"GPT4\"] = \"gpt4\";\n AIModel[\"GPT4_MINI\"] = \"gpt4-mini\";\n AIModel[\"CLAUDE_3_5\"] = \"claude-3.5\";\n AIModel[\"CLAUDE_SONNET_4_5\"] = \"claude-sonnet-4.5\";\n AIModel[\"CLAUDE_HAIKU_4_5\"] = \"claude-haiku-4.5\";\n AIModel[\"CLAUDE_OPUS_4_1\"] = \"claude-opus-4.1\";\n AIModel[\"GROK_2\"] = \"grok-2\";\n AIModel[\"LLAMA_4_SCOUT\"] = \"llama-4-scout\";\n AIModel[\"DEEPSEEK_V3\"] = \"deepseek-v3\";\n})(AIModel || (AIModel = {}));\n//# sourceMappingURL=AIModel.js.map","/**\n * Chromium launch flags used across Probo packages\n * These flags are carefully tested and should be reused consistently\n *\n * Used in:\n * - @probolabs/playwright/src/fixtures.ts\n * - @probolabs/recorder-app/src/config.ts\n */\nexport const PROBO_CHROMIUM_FLAGS = [\n '--disable-web-security', // Allow extensions to work properly\n '--disable-features=VizDisplayCompositor',\n // Anti-detection flags to match Chrome for Testing behavior\n '--disable-blink-features=AutomationControlled',\n '--disable-infobars',\n '--no-first-run',\n '--no-default-browser-check',\n // Download-related flags\n '--disable-pdf-viewer', // Force downloads instead of opening PDFs in viewer\n];\n//# sourceMappingURL=chromium-flags.js.map","import { test as base, chromium } from '@playwright/test';\nimport { readFileSync, mkdtempSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { tmpdir } from 'os';\nimport AdmZip from 'adm-zip';\nimport { fileURLToPath } from 'url';\nimport { PROBO_CHROMIUM_FLAGS } from '@probolabs/probo-shared';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Configuration options for Probo fixtures\n */\nexport interface ProboFixturesConfig {\n /** Whether to show browser console logs */\n showBrowserConsole?: boolean;\n /** Custom path to extensions directory */\n extensionsPath?: string;\n}\n\n/**\n * Extracts a CRX file to a temporary directory\n * Uses adm-zip with manual ZIP content detection\n */\nfunction extractCrxFile(crxPath: string): string {\n const crxBuffer = readFileSync(crxPath);\n \n // Find ZIP content by looking for ZIP magic number (PK)\n let zipStart = -1;\n for (let i = 0; i < crxBuffer.length - 4; i++) {\n if (crxBuffer[i] === 0x50 && crxBuffer[i + 1] === 0x4B) { // \"PK\"\n zipStart = i;\n break;\n }\n }\n \n if (zipStart === -1) {\n throw new Error('Could not find ZIP content in CRX file');\n }\n \n const zipBuffer = crxBuffer.subarray(zipStart);\n \n // Create temporary directory\n const tempDir = mkdtempSync(join(tmpdir(), 'probo-extension-'));\n \n // Extract ZIP content\n const zip = new AdmZip(zipBuffer);\n zip.extractAllTo(tempDir, true);\n \n return tempDir;\n}\n\n/**\n * Creates Probo fixtures that launch Chromium with extensions\n * \n * @param config Configuration options for the fixtures\n * @returns Extended test function with Probo fixtures\n */\nexport function createProboFixtures(config: ProboFixturesConfig = {}) {\n const { showBrowserConsole = false, extensionsPath } = config;\n \n // Default extensions path\n const defaultExtensionsPath = join(__dirname, '../loaded_extensions');\n const extensionsDir = extensionsPath || defaultExtensionsPath;\n\n return base.extend({\n context: async ({headless}, use) => {\n // Extract extensions\n const extensionDirs: string[] = [];\n \n try {\n const crxFile = join(extensionsDir, 'microsoft-single-sign-on.crx');\n const extractedDir = extractCrxFile(crxFile);\n extensionDirs.push(extractedDir);\n } catch (error) {\n console.warn('Could not load Microsoft SSO extension:', error);\n }\n \n // Launch Chromium with extensions\n const userDataDir = mkdtempSync(join(tmpdir(), 'probo-user-data-'));\n \n const context = await chromium.launchPersistentContext(userDataDir, {\n headless, // Extensions work better in non-headless mode\n args: [\n ...extensionDirs.map(dir => `--load-extension=${dir}`),\n '--disable-extensions-except=' + extensionDirs.join(','),\n ...PROBO_CHROMIUM_FLAGS\n ]\n });\n \n await use(context);\n \n // Cleanup\n await context.close();\n },\n \n page: async ({ context }, use) => {\n // Reuse existing page if available (launchPersistentContext creates one by default)\n // This prevents creating duplicate pages/tabs\n const pages = context.pages();\n const page = pages.length > 0 ? pages[0] : await context.newPage();\n \n // Set up console logging if requested\n if (showBrowserConsole) {\n page.on('console', msg => console.log('Browser console:', msg.text()));\n }\n \n await use(page);\n // Don't close the page if it's the default one from launchPersistentContext\n // Only close if we created a new one\n if (pages.length === 0) {\n await page.close();\n }\n },\n });\n}\n\n/**\n * Default Probo fixtures with standard configuration\n * Launches Chromium with Microsoft SSO extension\n */\nexport const test = createProboFixtures();\n\n/**\n * Re-export expect from Playwright for convenience\n */\nexport { expect } from '@playwright/test';\n"],"names":["fileURLToPath","dirname","readFileSync","mkdtempSync","join","tmpdir","base","chromium"],"mappings":";;;;;;;IAAO,IAAI,aAAa,CAAC;IACzB,CAAC,UAAU,aAAa,EAAE;IAC1B,IAAI,aAAa,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IACrD,IAAI,aAAa,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;IACzD,IAAI,aAAa,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IACrD,IAAI,aAAa,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IAC/C,IAAI,aAAa,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;IACnD,IAAI,aAAa,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IAC/C,IAAI,aAAa,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IACvD,IAAI,aAAa,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAAC;IAC/D,IAAI,aAAa,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;IAC7D,IAAI,aAAa,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IACrD,CAAC,EAAE,aAAa,KAAK,aAAa,GAAG,EAAE,CAAC,CAAC,CAAC;IACnC,IAAI,YAAY,CAAC;IACxB,CAAC,UAAU,YAAY,EAAE;IACzB,IAAI,YAAY,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;IAClD,IAAI,YAAY,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IACtD,IAAI,YAAY,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;IAClD,IAAI,YAAY,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IAC1D,CAAC,EAAE,YAAY,KAAK,YAAY,GAAG,EAAE,CAAC,CAAC;;ICnBvC;IACO,IAAI,gBAAgB,CAAC;IAC5B,CAAC,UAAU,gBAAgB,EAAE;IAC7B,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IAC1D,IAAI,gBAAgB,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;IAChD,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,IAAI,gBAAgB,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IAC5C,IAAI,gBAAgB,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;IAC5D,IAAI,gBAAgB,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAC;IAC9E,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IAC1D,IAAI,gBAAgB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;IACtD,IAAI,gBAAgB,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IACxD,IAAI,gBAAgB,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IAClD,IAAI,gBAAgB,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;IAChD,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,IAAI,gBAAgB,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAAC;IAClE,IAAI,gBAAgB,CAAC,uBAAuB,CAAC,GAAG,uBAAuB,CAAC;IACxE,IAAI,gBAAgB,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IAClD,IAAI,gBAAgB,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;IAChE,IAAI,gBAAgB,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IACxD,IAAI,gBAAgB,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;IAC1C,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IAC1D,IAAI,gBAAgB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;IACtD,IAAI,gBAAgB,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;IAC9C,IAAI,gBAAgB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;IACtD,IAAI,gBAAgB,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;IAC9C;IACA,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IAC1D,IAAI,gBAAgB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;IACtD,CAAC,EAAE,gBAAgB,KAAK,gBAAgB,GAAG,EAAE,CAAC,CAAC;;ICiH/C;IACA,IAAI,WAAW,CAAC;IAChB,CAAC,UAAU,WAAW,EAAE;IACxB,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;IAC/B,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACnC,CAAC,EAAE,WAAW,KAAK,WAAW,GAAG,EAAE,CAAC,CAAC;;ICpJrC;IACO,IAAI,qBAAqB,CAAC;IACjC,CAAC,UAAU,qBAAqB,EAAE;IAClC,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,GAAG,oCAAoC,CAAC;IACvG,IAAI,qBAAqB,CAAC,sCAAsC,CAAC,GAAG,sCAAsC,CAAC;IAC3G,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,GAAG,oCAAoC,CAAC;IACvG,IAAI,qBAAqB,CAAC,wCAAwC,CAAC,GAAG,wCAAwC,CAAC;IAC/G,IAAI,qBAAqB,CAAC,0CAA0C,CAAC,GAAG,0CAA0C,CAAC;IACnH,IAAI,qBAAqB,CAAC,wCAAwC,CAAC,GAAG,wCAAwC,CAAC;IAC/G,IAAI,qBAAqB,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAC;IACnF,IAAI,qBAAqB,CAAC,4BAA4B,CAAC,GAAG,4BAA4B,CAAC;IACvF,IAAI,qBAAqB,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAC;IACnF,IAAI,qBAAqB,CAAC,8BAA8B,CAAC,GAAG,8BAA8B,CAAC;IAC3F,IAAI,qBAAqB,CAAC,gCAAgC,CAAC,GAAG,gCAAgC,CAAC;IAC/F,IAAI,qBAAqB,CAAC,4BAA4B,CAAC,GAAG,4BAA4B,CAAC;IACvF,IAAI,qBAAqB,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAC;IACnF,IAAI,qBAAqB,CAAC,wCAAwC,CAAC,GAAG,wCAAwC,CAAC;IAC/G,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,GAAG,oCAAoC,CAAC;IACvG,IAAI,qBAAqB,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IAC7D,IAAI,qBAAqB,CAAC,sBAAsB,CAAC,GAAG,sBAAsB,CAAC;IAC3E,IAAI,qBAAqB,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAAC;IACvE,IAAI,qBAAqB,CAAC,yBAAyB,CAAC,GAAG,yBAAyB,CAAC;IACjF,IAAI,qBAAqB,CAAC,sBAAsB,CAAC,GAAG,sBAAsB,CAAC;IAC3E,IAAI,qBAAqB,CAAC,+BAA+B,CAAC,GAAG,+BAA+B,CAAC;IAC7F,CAAC,EAAE,qBAAqB,KAAK,qBAAqB,GAAG,EAAE,CAAC,CAAC;;ICvBzD;IACA;IACA;IACO,IAAI,aAAa,CAAC;IACzB,CAAC,UAAU,aAAa,EAAE;IAC1B,IAAI,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrC,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IACnC,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;IACjC,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IACnC,IAAI,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrC,CAAC,EAAE,aAAa,KAAK,aAAa,GAAG,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,aAAa,GAAG;IACtB,IAAI,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC;IAC5B,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC;IAC3B,IAAI,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC;IAC1B,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC;IAC3B,IAAI,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC;IAC5B,CAAC,CAAC;IACK,MAAM,WAAW,CAAC;IACzB,IAAI,WAAW,CAAC,MAAM,EAAE,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE;IACpD,QAAQ,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAC7B,QAAQ,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IAC3B,KAAK;IACL,IAAI,WAAW,CAAC,KAAK,EAAE;IACvB,QAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,wBAAwB,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5F,QAAQ,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IAC3B,KAAK;IACL,IAAI,SAAS,CAAC,KAAK,EAAE;IACrB,QAAQ,OAAO,aAAa,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjE,KAAK;IACL,IAAI,QAAQ,CAAC,KAAK,EAAE;IACpB,QAAQ,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAC/B,QAAQ,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,QAAQ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClE,QAAQ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClE,QAAQ,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC5E,QAAQ,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9F,KAAK;IACL,IAAI,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC;IAC5D,QAAQ,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;IACrE,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC;IAC1D,QAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;IACnE,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,CAAC;IACxD,QAAQ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;IACjE,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC;IAC1D,QAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;IACnE,IAAI,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC;IAC5D,QAAQ,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;IACrE;;IC/CyB,IAAI,WAAW,CAAC,WAAW;;ICFpD;IACA;IACA;IACO,IAAI,OAAO,CAAC;IACnB,CAAC,UAAU,OAAO,EAAE;IACpB,IAAI,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IACzC,IAAI,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;IACnD,IAAI,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IACrD,IAAI,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IACrD,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAC7B,IAAI,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;IACvC,IAAI,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IACzC,IAAI,OAAO,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;IACvD,IAAI,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IACrD,IAAI,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;IACnD,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;IACjC,IAAI,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;IAC/C,IAAI,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC;IAC3C,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,EAAE,CAAC,CAAC;;IClB7B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACO,MAAM,oBAAoB,GAAG;IACpC,IAAI,wBAAwB;IAC5B,IAAI,yCAAyC;IAC7C;IACA,IAAI,+CAA+C;IACnD,IAAI,oBAAoB;IACxB,IAAI,gBAAgB;IACpB,IAAI,4BAA4B;IAChC;IACA,IAAI,sBAAsB;IAC1B,CAAC;;ICVD,MAAM,UAAU,GAAGA,iBAAa,CAAC,mVAAe,CAAC,CAAC;IAClD,MAAM,SAAS,GAAGC,YAAO,CAAC,UAAU,CAAC,CAAC;IAYtC;;;IAGG;IACH,SAAS,cAAc,CAAC,OAAe,EAAA;IACrC,IAAA,MAAM,SAAS,GAAGC,eAAY,CAAC,OAAO,CAAC,CAAC;;IAGxC,IAAA,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;IAClB,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;IAC7C,QAAA,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE;gBACtD,QAAQ,GAAG,CAAC,CAAC;gBACb,MAAM;aACP;SACF;IAED,IAAA,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE;IACnB,QAAA,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;SAC3D;QAED,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;;IAG/C,IAAA,MAAM,OAAO,GAAGC,cAAW,CAACC,SAAI,CAACC,SAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;;IAGhE,IAAA,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,IAAA,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAEhC,IAAA,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;IAKG;IACa,SAAA,mBAAmB,CAAC,MAAA,GAA8B,EAAE,EAAA;QAClE,MAAM,EAAE,kBAAkB,GAAG,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;;QAG9D,MAAM,qBAAqB,GAAGD,SAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IACtE,IAAA,MAAM,aAAa,GAAG,cAAc,IAAI,qBAAqB,CAAC;QAE9D,OAAOE,WAAI,CAAC,MAAM,CAAC;YACjB,OAAO,EAAE,OAAO,EAAC,QAAQ,EAAC,EAAE,GAAG,KAAI;;gBAEjC,MAAM,aAAa,GAAa,EAAE,CAAC;IAEnC,YAAA,IAAI;oBACF,MAAM,OAAO,GAAGF,SAAI,CAAC,aAAa,EAAE,8BAA8B,CAAC,CAAC;IACpE,gBAAA,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAC7C,gBAAA,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;iBAClC;gBAAC,OAAO,KAAK,EAAE;IACd,gBAAA,OAAO,CAAC,IAAI,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAC;iBAChE;;IAGD,YAAA,MAAM,WAAW,GAAGD,cAAW,CAACC,SAAI,CAACC,SAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;gBAEpE,MAAM,OAAO,GAAG,MAAME,eAAQ,CAAC,uBAAuB,CAAC,WAAW,EAAE;IAClE,gBAAA,QAAQ;IACR,gBAAA,IAAI,EAAE;wBACJ,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA,iBAAA,EAAoB,GAAG,CAAA,CAAE,CAAC;IACtD,oBAAA,8BAA8B,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC;IACxD,oBAAA,GAAG,oBAAoB;IACxB,iBAAA;IACF,aAAA,CAAC,CAAC;IAEH,YAAA,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;;IAGnB,YAAA,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;aACvB;YAED,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,GAAG,KAAI;;;IAG/B,YAAA,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;;gBAGnE,IAAI,kBAAkB,EAAE;oBACtB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;iBACxE;IAED,YAAA,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;;;IAGhB,YAAA,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;IACtB,gBAAA,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;iBACpB;aACF;IACF,KAAA,CAAC,CAAC;IACL,CAAC;IAED;;;IAGG;AACU,UAAA,IAAI,GAAG,mBAAmB;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"fixtures.cjs","sources":["../../probo-shared/dist/Interaction.js","../../probo-shared/dist/PlaywrightAction.js","../../probo-shared/dist/element-utils.js","../../probo-shared/dist/MessageType.js","../../probo-shared/dist/utils.js","../../probo-shared/dist/api-client.js","../../probo-shared/dist/AIModel.js","../../probo-shared/dist/chromium-flags.js","../src/fixtures.ts"],"sourcesContent":["export var ApplyAIStatus;\n(function (ApplyAIStatus) {\n ApplyAIStatus[\"PREPARE_START\"] = \"PREPARE_START\";\n ApplyAIStatus[\"PREPARE_SUCCESS\"] = \"PREPARE_SUCCESS\";\n ApplyAIStatus[\"PREPARE_ERROR\"] = \"PREPARE_ERROR\";\n ApplyAIStatus[\"SEND_START\"] = \"SEND_START\";\n ApplyAIStatus[\"SEND_SUCCESS\"] = \"SEND_SUCCESS\";\n ApplyAIStatus[\"SEND_ERROR\"] = \"SEND_ERROR\";\n ApplyAIStatus[\"APPLY_AI_ERROR\"] = \"APPLY_AI_ERROR\";\n ApplyAIStatus[\"APPLY_AI_CANCELLED\"] = \"APPLY_AI_CANCELLED\";\n ApplyAIStatus[\"SUMMARY_COMPLETED\"] = \"SUMMARY_COMPLETED\";\n ApplyAIStatus[\"SUMMARY_ERROR\"] = \"SUMMARY_ERROR\";\n})(ApplyAIStatus || (ApplyAIStatus = {}));\nexport var ReplayStatus;\n(function (ReplayStatus) {\n ReplayStatus[\"REPLAY_START\"] = \"REPLAY_START\";\n ReplayStatus[\"REPLAY_SUCCESS\"] = \"REPLAY_SUCCESS\";\n ReplayStatus[\"REPLAY_ERROR\"] = \"REPLAY_ERROR\";\n ReplayStatus[\"REPLAY_CANCELLED\"] = \"REPLAY_CANCELLED\";\n})(ReplayStatus || (ReplayStatus = {}));\n//# sourceMappingURL=Interaction.js.map","// Action constants\nexport var PlaywrightAction;\n(function (PlaywrightAction) {\n PlaywrightAction[\"VISIT_BASE_URL\"] = \"VISIT_BASE_URL\";\n PlaywrightAction[\"VISIT_URL\"] = \"VISIT_URL\";\n PlaywrightAction[\"CLICK\"] = \"CLICK\";\n PlaywrightAction[\"FILL_IN\"] = \"FILL_IN\";\n PlaywrightAction[\"SELECT_DROPDOWN\"] = \"SELECT_DROPDOWN\";\n PlaywrightAction[\"SELECT_MULTIPLE_DROPDOWN\"] = \"SELECT_MULTIPLE_DROPDOWN\";\n PlaywrightAction[\"CHECK_CHECKBOX\"] = \"CHECK_CHECKBOX\";\n PlaywrightAction[\"SELECT_RADIO\"] = \"SELECT_RADIO\";\n PlaywrightAction[\"TOGGLE_SWITCH\"] = \"TOGGLE_SWITCH\";\n PlaywrightAction[\"SET_SLIDER\"] = \"SET_SLIDER\";\n PlaywrightAction[\"TYPE_KEYS\"] = \"TYPE_KEYS\";\n PlaywrightAction[\"HOVER\"] = \"HOVER\";\n PlaywrightAction[\"ASSERT_EXACT_VALUE\"] = \"ASSERT_EXACT_VALUE\";\n PlaywrightAction[\"ASSERT_CONTAINS_VALUE\"] = \"ASSERT_CONTAINS_VALUE\";\n PlaywrightAction[\"ASSERT_URL\"] = \"ASSERT_URL\";\n PlaywrightAction[\"SCROLL_TO_ELEMENT\"] = \"SCROLL_TO_ELEMENT\";\n PlaywrightAction[\"EXTRACT_VALUE\"] = \"EXTRACT_VALUE\";\n PlaywrightAction[\"ASK_AI\"] = \"ASK_AI\";\n PlaywrightAction[\"EXECUTE_SCRIPT\"] = \"EXECUTE_SCRIPT\";\n PlaywrightAction[\"UPLOAD_FILES\"] = \"UPLOAD_FILES\";\n PlaywrightAction[\"WAIT_FOR\"] = \"WAIT_FOR\";\n PlaywrightAction[\"WAIT_FOR_OTP\"] = \"WAIT_FOR_OTP\";\n PlaywrightAction[\"GEN_TOTP\"] = \"GEN_TOTP\";\n // Sequence marker actions (not real interactions, used only for grouping)\n PlaywrightAction[\"SEQUENCE_START\"] = \"SEQUENCE_START\";\n PlaywrightAction[\"SEQUENCE_END\"] = \"SEQUENCE_END\";\n})(PlaywrightAction || (PlaywrightAction = {}));\n//# sourceMappingURL=PlaywrightAction.js.map","import { ElementTag } from \"./ElementTag.js\";\nimport { PlaywrightAction } from \"./PlaywrightAction.js\";\n/**\n * Resolves an action type to the corresponding ElementTag\n *\n * @param action The action type (CLICK, FILL_IN, SELECT_DROPDOWN)\n * @returns The corresponding ElementTag\n */\nexport function resolveElementTag(action) {\n switch (action) {\n case PlaywrightAction.CLICK:\n return [ElementTag.CLICKABLE, ElementTag.FILLABLE];\n case PlaywrightAction.FILL_IN:\n return [ElementTag.FILLABLE];\n case PlaywrightAction.SELECT_DROPDOWN:\n return [ElementTag.SELECTABLE];\n case PlaywrightAction.HOVER:\n return [ElementTag.CLICKABLE, ElementTag.FILLABLE, ElementTag.NON_INTERACTIVE_ELEMENT];\n case PlaywrightAction.ASSERT_EXACT_VALUE:\n case PlaywrightAction.ASSERT_CONTAINS_VALUE:\n case PlaywrightAction.EXTRACT_VALUE:\n case PlaywrightAction.WAIT_FOR:\n return [ElementTag.CLICKABLE, ElementTag.FILLABLE, ElementTag.NON_INTERACTIVE_ELEMENT];\n case PlaywrightAction.WAIT_FOR_OTP:\n case PlaywrightAction.GEN_TOTP:\n return [ElementTag.FILLABLE];\n default:\n console.error(`Unknown action: ${action}`);\n throw new Error(`Unknown action: ${action}`);\n }\n}\nexport function getElementText(element) {\n var _a;\n return element.textContent || element.innerText || element.value || ((_a = element.querySelector('input')) === null || _a === void 0 ? void 0 : _a.value) || '';\n}\n/**\n * Checks if any string in the array matches the given regular expression pattern.\n *\n * @param array - Array of strings to test.\n * @param pattern - Regular expression to test against each string.\n * @returns true if at least one string matches the pattern, false otherwise.\n */\nfunction testArray(array, pattern) {\n return array.some(item => pattern.test(item));\n}\n/**\n * Determines if an element is fillable (can accept text input)\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is fillable\n */\nexport function isFillableElement(element) {\n var _a;\n if (!element)\n return false;\n // Check if it's an input element\n if (element.tagName === 'INPUT') {\n const inputType = (_a = element.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();\n const isOTPInput = element.getAttribute('data-input-otp') === 'true' ||\n element.getAttribute('autocomplete') === 'one-time-code';\n // if it's an OTP input, don't consider it fillable as it's handled by OTP logic\n if (isOTPInput)\n return false;\n // Include text, password, email, number, tel, url, search, date, datetime-local, month, week, time\n const fillableTypes = [\n 'text', 'password', 'email', 'number', 'tel', 'url', 'search',\n 'date', 'datetime-local', 'month', 'week', 'time'\n ];\n return fillableTypes.includes(inputType);\n }\n // Check if it's a textarea\n if (element.tagName === 'TEXTAREA') {\n return true;\n }\n // Check if it's a contenteditable element\n if (element.getAttribute('contenteditable') === 'true') {\n return true;\n }\n return false;\n}\n/**\n * Determines if an element is a selectable dropdown element\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is a select element\n */\nexport function isSelectableElement(element) {\n return element && element.tagName === 'SELECT';\n}\n/**\n * Determines if an element is a non-interactive element (not clickable, fillable, or selectable)\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is non-interactive\n */\nexport function isSliderElement(element) {\n if (!element)\n return false;\n // check for <input type=\"range\">\n if (element.tagName === 'INPUT' && element.getAttribute('type') === 'range')\n return true;\n // check for role and additional attributes\n const sliderAttributes = ['aria-valuenow', 'aria-valuemin', 'aria-valuemax'];\n if (element.getAttribute('role') === 'slider' || sliderAttributes.some(attr => element.hasAttribute(attr)))\n return true;\n // check for class names\n const classNames = Array.from(element.classList);\n const sliderClassPattern = /^MuiSlider|mat-slider|mdl-slider|ui-slider|carousel/;\n if (testArray(classNames, sliderClassPattern))\n return true;\n return false;\n}\n/**\n * Determines if an element is an <input type=\"file\"> element.\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is an input file element\n */\nexport function isInputFileElement(element) {\n return element && element.tagName === 'INPUT' && element.getAttribute('type') === 'file';\n}\n/**\n * Determines if an element is clickable\n *\n * @param element The DOM element to check\n * @returns boolean indicating if the element is fillable\n */\nexport function isClickableElement(element) {\n if (!element)\n return false;\n let depth = 0;\n while (depth < 5 && element && element.nodeType === Node.ELEMENT_NODE) {\n if (isClickableElementHelper(element) === IsClickable.YES)\n return true;\n if (isClickableElementHelper(element) === IsClickable.NO)\n return false;\n // if maybe, continue searching up to 5 levels up the DOM tree\n element = element.parentNode;\n depth++;\n }\n return false;\n}\n// clickable element detection result\nvar IsClickable;\n(function (IsClickable) {\n IsClickable[\"YES\"] = \"YES\";\n IsClickable[\"NO\"] = \"NO\";\n IsClickable[\"MAYBE\"] = \"MAYBE\";\n})(IsClickable || (IsClickable = {}));\nfunction isClickableElementHelper(element) {\n var _a, _b;\n if (!element)\n return IsClickable.NO;\n //check for tag name\n const tagName = element.tagName.toLowerCase();\n const clickableTags = [\n 'a', 'button',\n ];\n if (clickableTags.includes(tagName))\n return IsClickable.YES;\n //check for clickable <input>\n const inputType = (_a = element.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();\n const clickableTypes = [\n 'button', 'submit', 'reset', 'checkbox', 'radio',\n ];\n const ariaAutocompleteValues = [\n 'list', 'both',\n ];\n if (tagName === 'input') {\n if (clickableTypes.includes(inputType) || ariaAutocompleteValues.includes((_b = element.getAttribute('aria-autocomplete')) !== null && _b !== void 0 ? _b : ''))\n return IsClickable.YES;\n if (['date', 'number', 'range'].includes(inputType))\n return IsClickable.NO; //don't record the click as a change event will be generated for elements that generate an input change event\n }\n // check for cursor type\n const style = window.getComputedStyle(element);\n if (style.cursor === 'pointer')\n return IsClickable.YES;\n // check for attributes\n const clickableRoles = [\n 'button', 'combobox', 'listbox', 'dropdown', 'option', 'menu', 'menuitem',\n 'navigation', 'checkbox', 'switch', 'toggle', 'slider', 'textbox', 'listitem',\n 'presentation',\n ];\n const ariaPopupValues = [\n 'true', 'listbox', 'menu',\n ];\n if (element.hasAttribute('onclick') ||\n clickableRoles.includes(element.getAttribute('role') || '') ||\n ariaPopupValues.includes(element.getAttribute('aria-haspopup') || ''))\n return IsClickable.YES;\n // check for tabindex (means element is focusable and therefore clickable)\n if (parseInt(element.getAttribute('tabindex') || '-1') >= 0)\n return IsClickable.YES;\n // extract class names\n const classNames = Array.from(element.classList);\n // check for checkbox/radio-like class name - TODO: check if can be removed\n const checkboxPattern = /checkbox|switch|toggle|slider/i;\n if (testArray(classNames, checkboxPattern))\n return IsClickable.YES;\n // check for Material UI class names\n const muiClickableClassPattern = /MuiButton|MuiIconButton|MuiChip|MuiMenuItem|MuiListItem|MuiInputBase|MuiOutlinedInput|MuiSelect|MuiAutocomplete|MuiToggleButton|MuiBackdrop-root|MuiBackdrop-invisible/;\n if (testArray(classNames, muiClickableClassPattern))\n return IsClickable.YES;\n // check for SalesForce class names\n const sfClassPattern = /slds-button|slds-dropdown|slds-combobox|slds-picklist|slds-tabs|slds-pill|slds-action|slds-row-action|slds-context-bar|slds-input|slds-rich-text-area|slds-radio|slds-checkbox|slds-toggle|slds-link|slds-accordion|slds-tree/;\n if (testArray(classNames, sfClassPattern))\n return IsClickable.YES;\n // check for chart dots\n const chartClickableClassPattern = /recharts-dot/;\n if (testArray(classNames, chartClickableClassPattern))\n return IsClickable.YES;\n // check for React component classes\n const reactClickableClassPattern = /react-select|ant-select|rc-select|react-dropdown|react-autocomplete|react-datepicker|react-modal|react-tooltip|react-popover|react-menu|react-tabs|react-accordion|react-collapse|react-toggle|react-switch|react-checkbox|react-radio|react-button|react-link|react-card|react-list-item|react-menu-item|react-option|react-tab|react-panel|react-drawer|react-sidebar|react-nav|react-breadcrumb|react-pagination|react-stepper|react-wizard|react-carousel|react-slider|react-range|react-progress|react-badge|react-chip|react-tag|react-avatar|react-icon|react-fab|react-speed-dial|react-floating|react-sticky|react-affix|react-backdrop|react-overlay|react-portal|react-transition|react-animate|react-spring|react-framer|react-gesture|react-drag|react-drop|react-sortable|react-resizable|react-split|react-grid|react-table|react-datagrid|react-tree|react-treeview|react-file|react-upload|react-cropper|react-image|react-gallery|react-lightbox|react-player|react-video|react-audio|react-chart|react-graph|react-diagram|react-flow|react-d3|react-plotly|react-vega|react-vis|react-nivo|react-recharts|react-victory|react-echarts|react-highcharts|react-google-charts|react-fusioncharts|react-apexcharts|react-chartjs|react-chartkick|react-sparklines|react-trend|react-smooth|react-animated|react-lottie|react-spring|react-framer-motion|react-pose|react-motion|react-transition-group|react-router|react-navigation/i;\n if (testArray(classNames, reactClickableClassPattern))\n return IsClickable.YES;\n //check for cloudinary class names\n const cloudinaryClickableClassPattern = /cld-combobox|cld-upload-button|cld-controls|cld-player|cld-tab|cld-menu-item|cld-close|cld-play|cld-pause|cld-fullscreen|cld-browse|cld-cancel|cld-retry/;\n if (testArray(classNames, cloudinaryClickableClassPattern))\n return IsClickable.YES;\n return IsClickable.MAYBE;\n}\nexport function isTextInputElement(element) {\n return (element.tagName === 'INPUT' && element.getAttribute('type') !== 'checkbox' && element.getAttribute('type') !== 'radio') ||\n element.tagName === 'TEXTAREA' ||\n element.getAttribute('contenteditable') === 'true' ||\n element.getAttribute('role') === 'textbox';\n}\nexport function getParentNode(element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE)\n return null;\n let parent = null;\n // SF is using slots and shadow DOM heavily\n // However, there might be slots in the light DOM which shouldn't be traversed\n if (element.assignedSlot && element.getRootNode() instanceof ShadowRoot)\n parent = element.assignedSlot;\n else\n parent = element.parentNode;\n // Check if we're at a shadow root\n if (parent && parent.nodeType !== Node.ELEMENT_NODE && parent.getRootNode() instanceof ShadowRoot)\n parent = parent.getRootNode().host;\n return parent;\n}\nexport function getElementDepth(element) {\n let depth = 0;\n let currentElement = element;\n while ((currentElement === null || currentElement === void 0 ? void 0 : currentElement.nodeType) === Node.ELEMENT_NODE) {\n depth++;\n currentElement = getParentNode(currentElement);\n }\n return depth;\n}\n//# sourceMappingURL=element-utils.js.map","// WebSocketsMessageType enum for WebSocket and event message types shared across the app\nexport var WebSocketsMessageType;\n(function (WebSocketsMessageType) {\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_PREPARE_START\"] = \"INTERACTION_APPLY_AI_PREPARE_START\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_PREPARE_SUCCESS\"] = \"INTERACTION_APPLY_AI_PREPARE_SUCCESS\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_PREPARE_ERROR\"] = \"INTERACTION_APPLY_AI_PREPARE_ERROR\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SEND_TO_LLM_START\"] = \"INTERACTION_APPLY_AI_SEND_TO_LLM_START\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SEND_TO_LLM_SUCCESS\"] = \"INTERACTION_APPLY_AI_SEND_TO_LLM_SUCCESS\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SEND_TO_LLM_ERROR\"] = \"INTERACTION_APPLY_AI_SEND_TO_LLM_ERROR\";\n WebSocketsMessageType[\"INTERACTION_REPLAY_START\"] = \"INTERACTION_REPLAY_START\";\n WebSocketsMessageType[\"INTERACTION_REPLAY_SUCCESS\"] = \"INTERACTION_REPLAY_SUCCESS\";\n WebSocketsMessageType[\"INTERACTION_REPLAY_ERROR\"] = \"INTERACTION_REPLAY_ERROR\";\n WebSocketsMessageType[\"INTERACTION_REPLAY_CANCELLED\"] = \"INTERACTION_REPLAY_CANCELLED\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_CANCELLED\"] = \"INTERACTION_APPLY_AI_CANCELLED\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_ERROR\"] = \"INTERACTION_APPLY_AI_ERROR\";\n WebSocketsMessageType[\"INTERACTION_STEP_CREATED\"] = \"INTERACTION_STEP_CREATED\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SUMMARY_COMPLETED\"] = \"INTERACTION_APPLY_AI_SUMMARY_COMPLETED\";\n WebSocketsMessageType[\"INTERACTION_APPLY_AI_SUMMARY_ERROR\"] = \"INTERACTION_APPLY_AI_SUMMARY_ERROR\";\n WebSocketsMessageType[\"OTP_RETRIEVED\"] = \"OTP_RETRIEVED\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_START\"] = \"TEST_SUITE_RUN_START\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_LOG\"] = \"TEST_SUITE_RUN_LOG\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_COMPLETE\"] = \"TEST_SUITE_RUN_COMPLETE\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_ERROR\"] = \"TEST_SUITE_RUN_ERROR\";\n WebSocketsMessageType[\"TEST_SUITE_RUN_REPORTER_EVENT\"] = \"TEST_SUITE_RUN_REPORTER_EVENT\";\n})(WebSocketsMessageType || (WebSocketsMessageType = {}));\n//# sourceMappingURL=MessageType.js.map","import { PlaywrightAction } from './PlaywrightAction.js';\n/**\n * Logging levels for Probo\n */\nexport var ProboLogLevel;\n(function (ProboLogLevel) {\n ProboLogLevel[\"DEBUG\"] = \"DEBUG\";\n ProboLogLevel[\"INFO\"] = \"INFO\";\n ProboLogLevel[\"LOG\"] = \"LOG\";\n ProboLogLevel[\"WARN\"] = \"WARN\";\n ProboLogLevel[\"ERROR\"] = \"ERROR\";\n})(ProboLogLevel || (ProboLogLevel = {}));\nconst logLevelOrder = {\n [ProboLogLevel.DEBUG]: 0,\n [ProboLogLevel.INFO]: 1,\n [ProboLogLevel.LOG]: 2,\n [ProboLogLevel.WARN]: 3,\n [ProboLogLevel.ERROR]: 4,\n};\nexport class ProboLogger {\n constructor(prefix, level = ProboLogLevel.INFO) {\n this.prefix = prefix;\n this.level = level;\n }\n setLogLevel(level) {\n console.log(`[${this.prefix}] Setting log level to: ${level} (was: ${this.level})`);\n this.level = level;\n }\n shouldLog(level) {\n return logLevelOrder[level] >= logLevelOrder[this.level];\n }\n preamble(level) {\n const now = new Date();\n const hours = String(now.getHours()).padStart(2, '0');\n const minutes = String(now.getMinutes()).padStart(2, '0');\n const seconds = String(now.getSeconds()).padStart(2, '0');\n const milliseconds = String(now.getMilliseconds()).padStart(3, '0');\n return `[${hours}:${minutes}:${seconds}.${milliseconds}] [${this.prefix}] [${level}]`;\n }\n debug(...args) { if (this.shouldLog(ProboLogLevel.DEBUG))\n console.debug(this.preamble(ProboLogLevel.DEBUG), ...args); }\n info(...args) { if (this.shouldLog(ProboLogLevel.INFO))\n console.info(this.preamble(ProboLogLevel.INFO), ...args); }\n log(...args) { if (this.shouldLog(ProboLogLevel.LOG))\n console.log(this.preamble(ProboLogLevel.LOG), ...args); }\n warn(...args) { if (this.shouldLog(ProboLogLevel.WARN))\n console.warn(this.preamble(ProboLogLevel.WARN), ...args); }\n error(...args) { if (this.shouldLog(ProboLogLevel.ERROR))\n console.error(this.preamble(ProboLogLevel.ERROR), ...args); }\n}\n// Element cleaner logging\n// const elementLogger = new ProboLogger('element-cleaner');\n/**\n * Cleans and returns a minimal element info structure.\n */\n//TODO: is this needed?\n/* export function cleanupElementInfo(elementInfo: ElementInfo): CleanElementInfo {\n elementLogger.debug(\n `Cleaning up element info for ${elementInfo.tag} at index ${elementInfo.index}`\n );\n const depth = elementInfo.depth ?? elementInfo.getDepth();\n const cleanEl = {\n index: elementInfo.index,\n tag: elementInfo.tag,\n type: elementInfo.type,\n text: elementInfo.text,\n html: elementInfo.html,\n xpath: elementInfo.xpath,\n css_selector: elementInfo.css_selector,\n iframe_selector: elementInfo.iframe_selector,\n bounding_box: elementInfo.bounding_box,\n depth\n };\n elementLogger.debug(`Cleaned element: ${JSON.stringify(cleanEl)}`);\n return cleanEl;\n} */\n/**\n * Cleans highlighted elements in an instruction payload.\n */\n/* export function cleanupInstructionElements(instruction: any): any {\n if (!instruction?.result?.highlighted_elements) {\n elementLogger.debug('No highlighted elements to clean');\n return instruction;\n }\n elementLogger.debug(\n `Cleaning ${instruction.result.highlighted_elements.length} highlighted elements`\n );\n const cleaned = {\n ...instruction,\n result: {\n ...instruction.result,\n highlighted_elements: instruction.result.highlighted_elements.map(\n (el: ElementInfo) => cleanupElementInfo(el)\n )\n }\n };\n elementLogger.debug('Instruction cleaning completed');\n return cleaned;\n} */\n// Determine whether an interaction can return a value\nexport const hasReturnValue = (i) => {\n return [\n PlaywrightAction.EXTRACT_VALUE,\n PlaywrightAction.ASK_AI,\n PlaywrightAction.EXECUTE_SCRIPT\n ].includes(i.action);\n};\nexport const getReturnValueParameterName = (i) => {\n switch (i.action) {\n case PlaywrightAction.EXTRACT_VALUE:\n case PlaywrightAction.EXECUTE_SCRIPT:\n return i.parameterName;\n case PlaywrightAction.ASK_AI:\n return i.parameterName.replace(/^assert_/, '');\n default:\n console.error(`Action ${i.action} has no return value`);\n return '';\n }\n};\n// Determine whether an interaction can be parameterized\nexport const isParameterizable = (i) => {\n const parameterizableActions = [\n PlaywrightAction.FILL_IN,\n PlaywrightAction.SELECT_DROPDOWN,\n PlaywrightAction.SET_SLIDER,\n PlaywrightAction.ASSERT_CONTAINS_VALUE,\n PlaywrightAction.ASSERT_EXACT_VALUE,\n PlaywrightAction.VISIT_URL,\n PlaywrightAction.ASSERT_URL,\n PlaywrightAction.UPLOAD_FILES,\n PlaywrightAction.WAIT_FOR,\n PlaywrightAction.GEN_TOTP,\n PlaywrightAction.WAIT_FOR_OTP\n ];\n return parameterizableActions.includes(i.action) || (i.action === PlaywrightAction.ASK_AI && i.argument);\n};\n// Determine whether an interaction is AI-related\nexport const isAI = (i) => {\n var _a, _b, _c, _d, _e, _f;\n return !['TYPE_KEYS', 'VISIT_URL', 'EXECUTE_SCRIPT'].includes(i.action) &&\n (i.action === PlaywrightAction.ASK_AI ||\n (((_b = (_a = i.serverResponse) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.prompt) && (((_d = (_c = i.serverResponse) === null || _c === void 0 ? void 0 : _c.result) === null || _d === void 0 ? void 0 : _d.error) === \"\" || !((_f = (_e = i.serverResponse) === null || _e === void 0 ? void 0 : _e.result) === null || _f === void 0 ? void 0 : _f.error))));\n};\n// Determine whether an interaction is fortified (doesn't need AI processing)\nexport const isFortifiedInteraction = (i) => {\n return [\n PlaywrightAction.VISIT_URL,\n PlaywrightAction.ASSERT_URL,\n PlaywrightAction.TYPE_KEYS,\n PlaywrightAction.EXECUTE_SCRIPT,\n PlaywrightAction.ASK_AI\n ].includes(i.action);\n};\n/**\n * Check if an interaction is a sequence marker (SEQUENCE_START or SEQUENCE_END)\n * @param action - The PlaywrightAction to check\n * @returns true if the action is a sequence marker\n */\nexport function isSequenceMarker(action) {\n return action === PlaywrightAction.SEQUENCE_START || action === PlaywrightAction.SEQUENCE_END;\n}\nexport function singleQuoteString(str) {\n if (!str)\n return '';\n return `'${str.replace(/'/g, \"\\\\'\")}'`;\n}\nexport function doubleQuoteString(str) {\n if (!str)\n return '';\n return `\"${str.replace(/\"/g, '\\\\\"')}\"`;\n}\n/**\n * Extract unique process.env variable names from a parameter table.\n * Looks for ${process.env.VAR_NAME} patterns in string values.\n */\nexport function extractEnvVarsFromParameterTable(parameterTable) {\n if (!Array.isArray(parameterTable) || parameterTable.length === 0) {\n return [];\n }\n const envVars = new Set();\n const pattern = /\\$\\{process\\.env\\.([a-zA-Z_][a-zA-Z0-9_]*)\\}/g;\n parameterTable.forEach((row) => {\n Object.values(row).forEach((value) => {\n if (typeof value !== 'string')\n return;\n let match;\n while ((match = pattern.exec(value)) !== null) {\n envVars.add(match[1]);\n }\n });\n });\n return Array.from(envVars).sort();\n}\n/**\n * Converts a string to a filesystem-safe slug.\n * Used for filenames/package names (not URL parsing).\n */\nexport function slugify(text) {\n if (!text)\n return 'scenario';\n return text\n .toLowerCase()\n .trim()\n .replace(/[^a-zA-Z0-9-_]/g, '-') // Replace non-alphanumeric chars with hyphens\n .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen\n .replace(/^-|-$/g, '') // Remove leading/trailing hyphens\n .substring(0, 100); // Limit length to 100 chars\n}\n// Normalize a parameter name to a legal JS identifier\nexport function normalizeParameterName(name) {\n // 0. Remove leading/trailing spaces\n // 1. Replace invalid characters with underscores\n // 2. collapse multiple underscores\n // 3. Remove trailing underscores\n // 4. If starts with a digit, prepend '_'\n // 5. Convert to lowercase\n name = name.trim();\n name = name.replace(/[^a-zA-Z0-9_$]/g, '_');\n name = name.replace(/_+/g, '_');\n name = name.replace(/_+$/, ''); // Remove trailing underscores\n if (/^[0-9]/.test(name)) {\n name = '_' + name;\n }\n name = name.toLowerCase();\n // Ensure we never return an empty string or just underscores\n if (!name || name === '_' || /^_+$/.test(name)) {\n name = 'param_' + Date.now();\n }\n return name;\n}\nexport function matchRegex(str, regex) {\n //parse the regex string\n const match = regex.match(/^\\/(.+)\\/([gimsuy]*)$/);\n if (!match) // normal string\n return str.includes(regex);\n else { // regex string\n const pattern = match[1].replace(/\\\\/g, '\\\\');\n const flags = match[2];\n console.log(`Matching ${str} against ${pattern} with flags ${flags}`);\n return new RegExp(pattern, flags).test(str);\n }\n}\n/**\n * Truncates a URL by replacing query parameters (everything after ?) with three dots.\n * @param url The URL to truncate\n * @returns The truncated URL with three dots replacing query parameters\n */\nexport function truncateUrl(url) {\n if (!url)\n return '';\n const questionMarkIndex = url.indexOf('?');\n if (questionMarkIndex === -1) {\n // No query parameters found, return the original URL\n return url;\n }\n const baseUrl = url.substring(0, questionMarkIndex);\n return `${baseUrl}...`;\n}\n/**\n * Sets up browser console logging on a Playwright page.\n * This function removes all existing console listeners and optionally adds a new one.\n * @param page The Playwright page instance\n * @param enableConsoleLogs Whether to enable console logging\n * @param logger Optional logger instance to use for output (defaults to console.log)\n */\nexport const setupBrowserConsoleLogs = (page, enableConsoleLogs, logger = null) => {\n // Always remove all existing console listeners first\n page.removeAllListeners('console');\n // Always add a listener, but filter output based on enableConsoleLogs\n const listener = (msg) => {\n if (enableConsoleLogs) {\n const type = msg.type();\n const text = msg.text();\n if (logger) {\n logger.log(`[Browser-${type}]: ${text}`);\n }\n else {\n console.log(`[Browser-${type}]: ${text}`);\n }\n }\n // If disabled, do nothing (silently ignore the console message)\n };\n page.on('console', listener);\n};\n/**\n * Safely interpolates template literals in a string using the provided context.\n * Similar to JavaScript template literals, but executed safely at runtime.\n * Recursively interpolates until no more template literals remain.\n *\n * This function only interpolates the argument string itself, not the entire context.\n * When a template literal resolves to another string containing template literals,\n * it recursively interpolates that result.\n *\n * @param str The string containing template literal syntax (e.g., \"Hello ${name}\")\n * @param context The context object containing variables for interpolation\n * @param maxDepth Maximum recursion depth to prevent infinite loops (default: 10)\n * @returns The interpolated string, or the original string if no interpolation is needed\n */\nexport function interpolateTemplate(str, context, maxDepth = 10) {\n if (typeof str !== 'string' || !str.includes('${')) {\n return str;\n }\n if (maxDepth <= 0) {\n console.warn('⚠️ Maximum interpolation depth reached, returning partially interpolated string');\n return str;\n }\n try {\n // Escape backticks in the template to prevent template literal injection\n const escapedTemplate = str.replace(/\\\\/g, '\\\\\\\\').replace(/`/g, '\\\\`');\n // Create a safe template execution function\n // Assign all context properties to variables so they're accessible in the template\n // This works for both simple values and nested objects like process.env\n const contextKeys = Object.keys(context);\n const assignments = contextKeys.map(key => `const ${key} = ctx.${key};`).join('\\n');\n const functionBody = `\n const ctx = arguments[0];\n ${assignments}\n return \\`${escapedTemplate}\\`;\n `;\n const compiled = new Function(functionBody);\n const rendered = compiled(context);\n // If the result still contains template literals, recursively interpolate\n if (rendered.includes('${') && rendered !== str) {\n return interpolateTemplate(rendered, context, maxDepth - 1);\n }\n return rendered;\n }\n catch (e) {\n const error = e instanceof Error ? e : new Error(String(e));\n console.error('⚠️ Template evaluation failed:', error);\n throw error;\n }\n}\nexport class Mutex {\n constructor() {\n this._locked = false;\n this._waiters = [];\n }\n lock() {\n return new Promise((resolve) => {\n const unlock = () => {\n if (this._waiters.length > 0) {\n const nextResolve = this._waiters.shift();\n if (nextResolve) {\n nextResolve(unlock);\n }\n }\n else {\n this._locked = false;\n }\n };\n if (this._locked) {\n this._waiters.push(resolve);\n }\n else {\n this._locked = true;\n resolve(unlock);\n }\n });\n }\n async withLock(fn) {\n const unlock = await this.lock();\n try {\n return await fn();\n }\n finally {\n unlock();\n }\n }\n}\n//# sourceMappingURL=utils.js.map","import pRetry from 'p-retry';\nimport { ProboLogger } from './utils.js';\nexport const apiLogger = new ProboLogger('apiclient');\nexport class ApiError extends Error {\n constructor(status, message, data) {\n super(message);\n this.status = status;\n this.data = data;\n this.name = 'ApiError';\n // Remove stack trace for cleaner error messages\n this.stack = undefined;\n }\n toString() {\n return `${this.message} (Status: ${this.status})`;\n }\n}\nexport class ApiClient {\n constructor(apiUrl, token, maxRetries = 3, initialBackoff = 1000) {\n this.apiUrl = apiUrl;\n this.token = token;\n this.maxRetries = maxRetries;\n this.initialBackoff = initialBackoff;\n }\n /**\n * Determines if an error should be retried.\n * Only retries on timeout and network errors, not on client/server errors.\n */\n isRetryableError(error) {\n var _a, _b, _c, _d, _e;\n // Network/connection errors should be retried\n if ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('fetch failed'))\n return true;\n if ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('network'))\n return true;\n if ((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('ETIMEDOUT'))\n return true;\n if ((_d = error.message) === null || _d === void 0 ? void 0 : _d.includes('ECONNRESET'))\n return true;\n if ((_e = error.message) === null || _e === void 0 ? void 0 : _e.includes('ECONNREFUSED'))\n return true;\n if (error.code === 'ETIMEDOUT')\n return true;\n if (error.code === 'ECONNRESET')\n return true;\n if (error.code === 'ECONNREFUSED')\n return true;\n // If it's an ApiError, check the status code\n if (error instanceof ApiError) {\n // Retry on timeout-related status codes\n if (error.status === 408)\n return true; // Request Timeout\n if (error.status === 502)\n return true; // Bad Gateway (temporary)\n if (error.status === 503)\n return true; // Service Unavailable (temporary)\n if (error.status === 504)\n return true; // Gateway Timeout\n if (error.status === 0)\n return true; // Network error\n // Don't retry on client errors (4xx) or server errors (5xx)\n // These indicate problems that won't be fixed by retrying\n return false;\n }\n // For unknown errors, don't retry to avoid masking issues\n return false;\n }\n /**\n * Generic helper to wrap API requests with retry logic and consistent error handling.\n */\n async requestWithRetry(operationName, operation) {\n return pRetry(operation, {\n retries: this.maxRetries,\n minTimeout: this.initialBackoff,\n shouldRetry: (error) => {\n const shouldRetry = this.isRetryableError(error);\n if (!shouldRetry) {\n apiLogger.error(`${operationName} failed with non-retryable error: ${error.message || error}`);\n }\n return shouldRetry;\n },\n onFailedAttempt: error => {\n apiLogger.warn(`${operationName} failed (retryable), attempt ${error.attemptNumber} of ${error.retriesLeft + error.attemptNumber}. Error: ${error.message}`);\n }\n });\n }\n async handleResponse(response) {\n var _a;\n try {\n const data = await response.json();\n const error = `${(data === null || data === void 0 ? void 0 : data.error) || 'Unknown error'}`;\n apiLogger.debug(`API response: ${JSON.stringify(data)}`);\n if (!response.ok) {\n switch (response.status) {\n case 401:\n throw new ApiError(401, `Unauthorized - Invalid or missing authentication token: ${error}`);\n case 403:\n throw new ApiError(403, `Forbidden - You do not have permission to perform this action: ${error}`);\n case 400:\n throw new ApiError(400, `Bad Request: ${error}`);\n case 404:\n throw new ApiError(404, `Not Found: ${error}`);\n case 500:\n throw new ApiError(500, `Internal Server Error: ${error}`);\n default:\n throw new ApiError(response.status, `API Error: ${error}`);\n }\n }\n return data;\n }\n catch (error) {\n // Only throw the original error if it's not a network error\n if (!((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('fetch failed'))) {\n throw error;\n }\n throw new ApiError(0, 'Network error: fetch failed');\n }\n }\n getHeaders() {\n const headers = {\n 'Content-Type': 'application/json',\n };\n // Always include token in headers now that we have a default\n headers['Authorization'] = `Token ${this.token}`;\n return headers;\n }\n async createStep(options) {\n apiLogger.debug('creating step ', options.stepPrompt);\n return this.requestWithRetry('createStep', async () => {\n const response = await fetch(`${this.apiUrl}/step-runners/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n step_id: options.stepIdFromServer,\n scenario_name: options.scenarioName,\n step_prompt: options.stepPrompt,\n argument: options.argument,\n initial_screenshot: options.initial_screenshot_url,\n initial_html_content: options.initial_html_content,\n use_cache: options.use_cache,\n url: options.url,\n action: options.action,\n vanilla_prompt: options.vanilla_prompt,\n is_vanilla_prompt_robust: options.is_vanilla_prompt_robust,\n target_element_name: options.target_element_name,\n position: options.position\n }),\n });\n const data = await this.handleResponse(response);\n return data.step.id;\n });\n }\n async patchStep(stepId, fields) {\n // Use PATCH /steps/:id/ endpoint with partial=true\n apiLogger.debug(`patching step #${stepId} with fields:`, Object.keys(fields));\n return this.requestWithRetry('patchStep', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/`, {\n method: 'PATCH',\n headers: this.getHeaders(),\n body: JSON.stringify(fields)\n });\n const data = await this.handleResponse(response);\n return;\n });\n }\n /* async resolveNextInstruction(stepId: string, instruction: Instruction | null, aiModel?: string) {\n apiLogger.debug(`resolving next instruction: ${instruction}`);\n return this.requestWithRetry('resolveNextInstruction', async () => {\n apiLogger.debug(`API client: Resolving next instruction for step ${stepId}`);\n \n const cleanInstruction = cleanupInstructionElements(instruction);\n \n const response = await fetch(`${this.apiUrl}/step-runners/${stepId}/run/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n executed_instruction: cleanInstruction,\n ai_model: aiModel\n }),\n });\n \n const data = await this.handleResponse(response);\n return data.instruction;\n });\n } */\n async uploadScreenshot(screenshot_bytes) {\n return this.requestWithRetry('uploadScreenshot', async () => {\n const response = await fetch(`${this.apiUrl}/upload-screenshots/`, {\n method: 'POST',\n headers: {\n 'Authorization': `Token ${this.token}`\n },\n body: screenshot_bytes,\n });\n const data = await this.handleResponse(response);\n return data.screenshot_url;\n });\n }\n async findStepById(stepId) {\n apiLogger.debug(`Finding step by id: ${stepId}`);\n return this.requestWithRetry('findStepById', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/`, {\n method: 'GET',\n headers: this.getHeaders(),\n });\n const data = await this.handleResponse(response);\n return data.step;\n });\n }\n async findStepByPrompt(prompt, scenarioName, url = '') {\n apiLogger.debug(`Finding step by prompt: ${prompt} and scenario: ${scenarioName}`);\n return this.requestWithRetry('findStepByPrompt', async () => {\n const response = await fetch(`${this.apiUrl}/step-runners/find-step-by-prompt/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n prompt: prompt,\n scenario_name: scenarioName,\n url: url\n }),\n });\n try {\n const data = await this.handleResponse(response);\n return {\n step: data.step,\n total_count: data.total_count\n };\n }\n catch (error) {\n // If we get a 404, the step doesn't exist\n if (error instanceof ApiError && error.status === 404) {\n return null;\n }\n // For any other error, rethrow\n throw error;\n }\n });\n }\n async resetStep(stepId) {\n return this.requestWithRetry('resetStep', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/reset/`, {\n method: 'POST',\n headers: this.getHeaders(),\n });\n const data = await this.handleResponse(response);\n return data;\n });\n }\n async interactionToStep(scenarioId, interaction, position = -1) {\n // Use POST /interaction-to-step/ endpoint\n // Backend will create new step or update existing one based on interaction_id\n apiLogger.debug(`converting interaction #${interaction.interactionId} to step`);\n return this.requestWithRetry('interactionToStep', async () => {\n var _a, _b, _c, _d;\n const response = await fetch(`${this.apiUrl}/interaction-to-step/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n scenario_id: scenarioId,\n position,\n interaction_id: interaction.interactionId,\n action: interaction.action,\n argument: interaction.argument,\n element_css_selector: ((_a = interaction.elementInfo) === null || _a === void 0 ? void 0 : _a.css_selector) || '',\n iframe_selector: ((_b = interaction.elementInfo) === null || _b === void 0 ? void 0 : _b.iframe_selector) || '',\n smart_selector: ((_c = interaction.elementInfo) === null || _c === void 0 ? void 0 : _c.smart_selector) || null,\n smart_iframe_selector: ((_d = interaction.elementInfo) === null || _d === void 0 ? void 0 : _d.smart_iframe_selector) || null,\n prompt: interaction.nativeDescription,\n vanilla_prompt: interaction.nativeDescription,\n is_vanilla_prompt_robust: interaction.isNativeDescriptionElaborate || false,\n target_element_name: interaction.nativeName,\n target_element_info: interaction.elementInfo,\n url: interaction.url,\n is_secret: interaction.isSecret || false,\n })\n });\n const data = await this.handleResponse(response);\n return [data.result.step_id, data.result.matched_step, data.scenario_id];\n });\n }\n async actionToPrompt(action2promptInput, aiModel) {\n // Use POST /action-to-prompt/ endpoint\n apiLogger.debug(`running action2prompt for step #${action2promptInput.step_id}`);\n return this.requestWithRetry('actionToPrompt', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${action2promptInput.step_id}/action_to_prompt/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ ...action2promptInput, 'model': aiModel })\n });\n const data = await this.handleResponse(response);\n return data;\n });\n }\n async summarizeScenario(scenarioId, aiModel) {\n return this.requestWithRetry('summarizeScenario', async () => {\n const response = await fetch(`${this.apiUrl}/api/scenarios/${scenarioId}/summary`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ 'model': aiModel })\n });\n const data = await this.handleResponse(response);\n return data;\n });\n }\n async askAI(question, scenarioName, screenshot, aiModel) {\n apiLogger.debug(`Asking AI question: \"${question}\", scenarioName: ${scenarioName}`);\n apiLogger.debug(`headers: ${JSON.stringify(this.getHeaders())}`);\n return this.requestWithRetry('askAI', async () => {\n const response = await fetch(`${this.apiUrl}/api/ask-ai/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n question: question,\n scenario_name: scenarioName,\n screenshot: screenshot,\n model: aiModel\n }),\n });\n const data = await this.handleResponse(response);\n return data;\n });\n }\n async screenshotReasoning(stepId, prompt, aiModel) {\n apiLogger.debug(`Performing screenshot reasoning for step: ${stepId}`);\n return this.requestWithRetry('screenshotReasoning', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/screenshot_reasoning/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n prompt: prompt,\n model: aiModel\n }),\n });\n const data = await this.handleResponse(response);\n return data.action;\n });\n }\n async findBestCandidateElement(stepId, candidatesScreenshotUrl, candidateElements, aiModel) {\n apiLogger.debug(`Finding best candidate element for step: ${stepId}`);\n return this.requestWithRetry('findBestCandidateElement', async () => {\n const response = await fetch(`${this.apiUrl}/steps/${stepId}/find_best_candidate_element/`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n screenshot_url: candidatesScreenshotUrl,\n candidate_elements: candidateElements,\n model: aiModel\n }),\n });\n const data = await this.handleResponse(response);\n return data.index;\n });\n }\n} /* ApiClient */\n//# sourceMappingURL=api-client.js.map","/**\n * Available AI models for LLM operations\n */\nexport var AIModel;\n(function (AIModel) {\n AIModel[\"AZURE_GPT4\"] = \"azure-gpt4\";\n AIModel[\"AZURE_GPT4_MINI\"] = \"azure-gpt4-mini\";\n AIModel[\"GEMINI_1_5_FLASH\"] = \"gemini-1.5-flash\";\n AIModel[\"GEMINI_2_5_FLASH\"] = \"gemini-2.5-flash\";\n AIModel[\"GPT4\"] = \"gpt4\";\n AIModel[\"GPT4_MINI\"] = \"gpt4-mini\";\n AIModel[\"CLAUDE_3_5\"] = \"claude-3.5\";\n AIModel[\"CLAUDE_SONNET_4_5\"] = \"claude-sonnet-4.5\";\n AIModel[\"CLAUDE_HAIKU_4_5\"] = \"claude-haiku-4.5\";\n AIModel[\"CLAUDE_OPUS_4_1\"] = \"claude-opus-4.1\";\n AIModel[\"GROK_2\"] = \"grok-2\";\n AIModel[\"LLAMA_4_SCOUT\"] = \"llama-4-scout\";\n AIModel[\"DEEPSEEK_V3\"] = \"deepseek-v3\";\n})(AIModel || (AIModel = {}));\n//# sourceMappingURL=AIModel.js.map","/**\n * Chromium launch flags used across Probo packages\n * These flags are carefully tested and should be reused consistently\n *\n * Used in:\n * - @probolabs/playwright/src/fixtures.ts\n * - @probolabs/recorder-app/src/config.ts\n */\nexport const PROBO_CHROMIUM_FLAGS = [\n '--disable-web-security', // Allow extensions to work properly\n '--disable-features=VizDisplayCompositor',\n // Anti-detection flags to match Chrome for Testing behavior\n '--disable-blink-features=AutomationControlled',\n '--disable-infobars',\n '--no-first-run',\n '--no-default-browser-check',\n // Download-related flags\n '--disable-pdf-viewer', // Force downloads instead of opening PDFs in viewer\n];\n//# sourceMappingURL=chromium-flags.js.map","import { test as base, chromium } from '@playwright/test';\nimport { readFileSync, mkdtempSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { tmpdir } from 'os';\nimport AdmZip from 'adm-zip';\nimport { fileURLToPath } from 'url';\nimport { PROBO_CHROMIUM_FLAGS } from '@probolabs/probo-shared';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Configuration options for Probo fixtures\n */\nexport interface ProboFixturesConfig {\n /** Whether to show browser console logs */\n showBrowserConsole?: boolean;\n /** Custom path to extensions directory */\n extensionsPath?: string;\n}\n\n/**\n * Extracts a CRX file to a temporary directory\n * Uses adm-zip with manual ZIP content detection\n */\nfunction extractCrxFile(crxPath: string): string {\n const crxBuffer = readFileSync(crxPath);\n \n // Find ZIP content by looking for ZIP magic number (PK)\n let zipStart = -1;\n for (let i = 0; i < crxBuffer.length - 4; i++) {\n if (crxBuffer[i] === 0x50 && crxBuffer[i + 1] === 0x4B) { // \"PK\"\n zipStart = i;\n break;\n }\n }\n \n if (zipStart === -1) {\n throw new Error('Could not find ZIP content in CRX file');\n }\n \n const zipBuffer = crxBuffer.subarray(zipStart);\n \n // Create temporary directory\n const tempDir = mkdtempSync(join(tmpdir(), 'probo-extension-'));\n \n // Extract ZIP content\n const zip = new AdmZip(zipBuffer);\n zip.extractAllTo(tempDir, true);\n \n return tempDir;\n}\n\n/**\n * Creates Probo fixtures that launch Chromium with extensions\n * \n * @param config Configuration options for the fixtures\n * @returns Extended test function with Probo fixtures\n */\nexport function createProboFixtures(config: ProboFixturesConfig = {}) {\n const { showBrowserConsole = false, extensionsPath } = config;\n \n // Default extensions path\n const defaultExtensionsPath = join(__dirname, '../loaded_extensions');\n const extensionsDir = extensionsPath || defaultExtensionsPath;\n\n return base.extend({\n context: async ({headless}, use) => {\n // Extract extensions\n const extensionDirs: string[] = [];\n \n try {\n const crxFile = join(extensionsDir, 'microsoft-single-sign-on.crx');\n const extractedDir = extractCrxFile(crxFile);\n extensionDirs.push(extractedDir);\n } catch (error) {\n console.warn('Could not load Microsoft SSO extension:', error);\n }\n \n // Launch Chromium with extensions\n const userDataDir = mkdtempSync(join(tmpdir(), 'probo-user-data-'));\n \n const context = await chromium.launchPersistentContext(userDataDir, {\n headless, // Extensions work better in non-headless mode\n args: [\n ...extensionDirs.map(dir => `--load-extension=${dir}`),\n '--disable-extensions-except=' + extensionDirs.join(','),\n ...PROBO_CHROMIUM_FLAGS\n ]\n });\n \n await use(context);\n \n // Cleanup\n await context.close();\n },\n \n page: async ({ context }, use) => {\n // Reuse existing page if available (launchPersistentContext creates one by default)\n // This prevents creating duplicate pages/tabs\n const pages = context.pages();\n const page = pages.length > 0 ? pages[0] : await context.newPage();\n \n // Set up console logging if requested\n if (showBrowserConsole) {\n page.on('console', msg => console.log('Browser console:', msg.text()));\n }\n \n await use(page);\n // Don't close the page if it's the default one from launchPersistentContext\n // Only close if we created a new one\n if (pages.length === 0) {\n await page.close();\n }\n },\n });\n}\n\n/**\n * Default Probo fixtures with standard configuration\n * Launches Chromium with Microsoft SSO extension\n */\nexport const test = createProboFixtures();\n\n/**\n * Re-export expect from Playwright for convenience\n */\nexport { expect } from '@playwright/test';\n"],"names":["fileURLToPath","dirname","readFileSync","mkdtempSync","join","tmpdir","base","chromium"],"mappings":"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"}
|