@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.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixtures.js","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":["base"],"mappings":";;;;;;;;AAAO,IAAI,aAAa,CAAC;AACzB,CAAC,UAAU,aAAa,EAAE;AAC1B,IAAI,aAAa,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;AACrD,IAAI,aAAa,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;AACzD,IAAI,aAAa,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;AACrD,IAAI,aAAa,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAC/C,IAAI,aAAa,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AACnD,IAAI,aAAa,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAC/C,IAAI,aAAa,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;AACvD,IAAI,aAAa,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAAC;AAC/D,IAAI,aAAa,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;AAC7D,IAAI,aAAa,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;AACrD,CAAC,EAAE,aAAa,KAAK,aAAa,GAAG,EAAE,CAAC,CAAC,CAAC;AACnC,IAAI,YAAY,CAAC;AACxB,CAAC,UAAU,YAAY,EAAE;AACzB,IAAI,YAAY,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAClD,IAAI,YAAY,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;AACtD,IAAI,YAAY,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAClD,IAAI,YAAY,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;AAC1D,CAAC,EAAE,YAAY,KAAK,YAAY,GAAG,EAAE,CAAC,CAAC;;ACnBvC;AACO,IAAI,gBAAgB,CAAC;AAC5B,CAAC,UAAU,gBAAgB,EAAE;AAC7B,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;AAC1D,IAAI,gBAAgB,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;AAChD,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AACxC,IAAI,gBAAgB,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;AAC5C,IAAI,gBAAgB,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;AAC5D,IAAI,gBAAgB,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAC;AAC9E,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;AAC1D,IAAI,gBAAgB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AACtD,IAAI,gBAAgB,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;AACxD,IAAI,gBAAgB,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAClD,IAAI,gBAAgB,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;AAChD,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AACxC,IAAI,gBAAgB,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAAC;AAClE,IAAI,gBAAgB,CAAC,uBAAuB,CAAC,GAAG,uBAAuB,CAAC;AACxE,IAAI,gBAAgB,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAClD,IAAI,gBAAgB,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;AAChE,IAAI,gBAAgB,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;AACxD,IAAI,gBAAgB,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AAC1C,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;AAC1D,IAAI,gBAAgB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AACtD,IAAI,gBAAgB,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;AAC9C,IAAI,gBAAgB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AACtD,IAAI,gBAAgB,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;AAC9C;AACA,IAAI,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;AAC1D,IAAI,gBAAgB,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AACtD,CAAC,EAAE,gBAAgB,KAAK,gBAAgB,GAAG,EAAE,CAAC,CAAC;;ACiH/C;AACA,IAAI,WAAW,CAAC;AAChB,CAAC,UAAU,WAAW,EAAE;AACxB,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;AAC/B,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAC7B,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AACnC,CAAC,EAAE,WAAW,KAAK,WAAW,GAAG,EAAE,CAAC,CAAC;;ACpJrC;AACO,IAAI,qBAAqB,CAAC;AACjC,CAAC,UAAU,qBAAqB,EAAE;AAClC,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,GAAG,oCAAoC,CAAC;AACvG,IAAI,qBAAqB,CAAC,sCAAsC,CAAC,GAAG,sCAAsC,CAAC;AAC3G,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,GAAG,oCAAoC,CAAC;AACvG,IAAI,qBAAqB,CAAC,wCAAwC,CAAC,GAAG,wCAAwC,CAAC;AAC/G,IAAI,qBAAqB,CAAC,0CAA0C,CAAC,GAAG,0CAA0C,CAAC;AACnH,IAAI,qBAAqB,CAAC,wCAAwC,CAAC,GAAG,wCAAwC,CAAC;AAC/G,IAAI,qBAAqB,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAC;AACnF,IAAI,qBAAqB,CAAC,4BAA4B,CAAC,GAAG,4BAA4B,CAAC;AACvF,IAAI,qBAAqB,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAC;AACnF,IAAI,qBAAqB,CAAC,8BAA8B,CAAC,GAAG,8BAA8B,CAAC;AAC3F,IAAI,qBAAqB,CAAC,gCAAgC,CAAC,GAAG,gCAAgC,CAAC;AAC/F,IAAI,qBAAqB,CAAC,4BAA4B,CAAC,GAAG,4BAA4B,CAAC;AACvF,IAAI,qBAAqB,CAAC,0BAA0B,CAAC,GAAG,0BAA0B,CAAC;AACnF,IAAI,qBAAqB,CAAC,wCAAwC,CAAC,GAAG,wCAAwC,CAAC;AAC/G,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,GAAG,oCAAoC,CAAC;AACvG,IAAI,qBAAqB,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;AAC7D,IAAI,qBAAqB,CAAC,sBAAsB,CAAC,GAAG,sBAAsB,CAAC;AAC3E,IAAI,qBAAqB,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAAC;AACvE,IAAI,qBAAqB,CAAC,yBAAyB,CAAC,GAAG,yBAAyB,CAAC;AACjF,IAAI,qBAAqB,CAAC,sBAAsB,CAAC,GAAG,sBAAsB,CAAC;AAC3E,IAAI,qBAAqB,CAAC,+BAA+B,CAAC,GAAG,+BAA+B,CAAC;AAC7F,CAAC,EAAE,qBAAqB,KAAK,qBAAqB,GAAG,EAAE,CAAC,CAAC;;ACvBzD;AACA;AACA;AACO,IAAI,aAAa,CAAC;AACzB,CAAC,UAAU,aAAa,EAAE;AAC1B,IAAI,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AACrC,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AACnC,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;AACjC,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AACnC,IAAI,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AACrC,CAAC,EAAE,aAAa,KAAK,aAAa,GAAG,EAAE,CAAC,CAAC,CAAC;AAC1C,MAAM,aAAa,GAAG;AACtB,IAAI,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC;AAC5B,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC;AAC3B,IAAI,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC;AAC1B,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC;AAC3B,IAAI,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC;AAC5B,CAAC,CAAC;AACK,MAAM,WAAW,CAAC;AACzB,IAAI,WAAW,CAAC,MAAM,EAAE,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE;AACpD,QAAQ,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;AAC7B,QAAQ,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;AAC3B,KAAK;AACL,IAAI,WAAW,CAAC,KAAK,EAAE;AACvB,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;AAC5F,QAAQ,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;AAC3B,KAAK;AACL,IAAI,SAAS,CAAC,KAAK,EAAE;AACrB,QAAQ,OAAO,aAAa,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACjE,KAAK;AACL,IAAI,QAAQ,CAAC,KAAK,EAAE;AACpB,QAAQ,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;AAC/B,QAAQ,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC9D,QAAQ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAClE,QAAQ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAClE,QAAQ,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC5E,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;AAC9F,KAAK;AACL,IAAI,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC;AAC5D,QAAQ,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;AACrE,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC;AAC1D,QAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;AACnE,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,CAAC;AACxD,QAAQ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;AACjE,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC;AAC1D,QAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;AACnE,IAAI,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC;AAC5D,QAAQ,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE;AACrE;;AC/CyB,IAAI,WAAW,CAAC,WAAW;;ACFpD;AACA;AACA;AACO,IAAI,OAAO,CAAC;AACnB,CAAC,UAAU,OAAO,EAAE;AACpB,IAAI,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AACzC,IAAI,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;AACnD,IAAI,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;AACrD,IAAI,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;AACrD,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AAC7B,IAAI,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;AACvC,IAAI,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AACzC,IAAI,OAAO,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;AACvD,IAAI,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;AACrD,IAAI,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC;AACnD,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AACjC,IAAI,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC;AAC/C,IAAI,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC;AAC3C,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,EAAE,CAAC,CAAC;;AClB7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAM,oBAAoB,GAAG;AACpC,IAAI,wBAAwB;AAC5B,IAAI,yCAAyC;AAC7C;AACA,IAAI,+CAA+C;AACnD,IAAI,oBAAoB;AACxB,IAAI,gBAAgB;AACpB,IAAI,4BAA4B;AAChC;AACA,IAAI,sBAAsB;AAC1B,CAAC;;ACVD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAYtC;;;AAGG;AACH,SAAS,cAAc,CAAC,OAAe,EAAA;AACrC,IAAA,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;;AAGxC,IAAA,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;AAClB,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;AAC7C,QAAA,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE;YACtD,QAAQ,GAAG,CAAC,CAAC;YACb,MAAM;SACP;KACF;AAED,IAAA,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE;AACnB,QAAA,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;KAC3D;IAED,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;;AAG/C,IAAA,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;;AAGhE,IAAA,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC;AAClC,IAAA,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAEhC,IAAA,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;AAKG;AACa,SAAA,mBAAmB,CAAC,MAAA,GAA8B,EAAE,EAAA;IAClE,MAAM,EAAE,kBAAkB,GAAG,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;;IAG9D,MAAM,qBAAqB,GAAG,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;AACtE,IAAA,MAAM,aAAa,GAAG,cAAc,IAAI,qBAAqB,CAAC;IAE9D,OAAOA,MAAI,CAAC,MAAM,CAAC;QACjB,OAAO,EAAE,OAAO,EAAC,QAAQ,EAAC,EAAE,GAAG,KAAI;;YAEjC,MAAM,aAAa,GAAa,EAAE,CAAC;AAEnC,YAAA,IAAI;gBACF,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,8BAA8B,CAAC,CAAC;AACpE,gBAAA,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;AAC7C,gBAAA,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;aAClC;YAAC,OAAO,KAAK,EAAE;AACd,gBAAA,OAAO,CAAC,IAAI,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAC;aAChE;;AAGD,YAAA,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;YAEpE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,uBAAuB,CAAC,WAAW,EAAE;AAClE,gBAAA,QAAQ;AACR,gBAAA,IAAI,EAAE;oBACJ,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA,iBAAA,EAAoB,GAAG,CAAA,CAAE,CAAC;AACtD,oBAAA,8BAA8B,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC;AACxD,oBAAA,GAAG,oBAAoB;AACxB,iBAAA;AACF,aAAA,CAAC,CAAC;AAEH,YAAA,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;;AAGnB,YAAA,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;SACvB;QAED,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,GAAG,KAAI;;;AAG/B,YAAA,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;;YAGnE,IAAI,kBAAkB,EAAE;gBACtB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;aACxE;AAED,YAAA,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;;;AAGhB,YAAA,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;AACtB,gBAAA,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;aACpB;SACF;AACF,KAAA,CAAC,CAAC;AACL,CAAC;AAED;;;AAGG;AACU,MAAA,IAAI,GAAG,mBAAmB;;;;"}
|
|
1
|
+
{"version":3,"file":"fixtures.js","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":["base"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1675,7 +1675,6 @@ export default defineConfig({
|
|
|
1675
1675
|
// Keep Playwright's default console output, plus HTML report${includeReporter ? ', plus Probo live progress reporter' : ''}.
|
|
1676
1676
|
reporter: ${JSON.stringify(reporters)},
|
|
1677
1677
|
use: {
|
|
1678
|
-
headless: true,
|
|
1679
1678
|
ignoreHTTPSErrors: true,
|
|
1680
1679
|
actionTimeout: 30000,
|
|
1681
1680
|
navigationTimeout: 30000,
|
|
@@ -1684,7 +1683,6 @@ export default defineConfig({
|
|
|
1684
1683
|
trace: 'on',
|
|
1685
1684
|
},
|
|
1686
1685
|
retries: 0,
|
|
1687
|
-
workers: 1,
|
|
1688
1686
|
});
|
|
1689
1687
|
`;
|
|
1690
1688
|
}
|
|
@@ -5869,6 +5867,19 @@ export default class ProboReporter implements Reporter {
|
|
|
5869
5867
|
static getDefaultViewPort() {
|
|
5870
5868
|
return { width: 1280, height: 720 };
|
|
5871
5869
|
}
|
|
5870
|
+
/**
|
|
5871
|
+
* Validate and normalize viewport dimensions
|
|
5872
|
+
* Returns a valid viewport object with numeric width and height, falling back to defaults if invalid
|
|
5873
|
+
*/
|
|
5874
|
+
static normalizeViewPort(viewPort) {
|
|
5875
|
+
const defaultViewPort = this.getDefaultViewPort();
|
|
5876
|
+
if (!viewPort) {
|
|
5877
|
+
return defaultViewPort;
|
|
5878
|
+
}
|
|
5879
|
+
const width = typeof viewPort.width === 'number' && !isNaN(viewPort.width) ? viewPort.width : defaultViewPort.width;
|
|
5880
|
+
const height = typeof viewPort.height === 'number' && !isNaN(viewPort.height) ? viewPort.height : defaultViewPort.height;
|
|
5881
|
+
return { width, height };
|
|
5882
|
+
}
|
|
5872
5883
|
/**
|
|
5873
5884
|
* Generate code for a single scenario
|
|
5874
5885
|
* Fetches scenario data, converts to Interaction[], generates code
|
|
@@ -5887,7 +5898,7 @@ export default class ProboReporter implements Reporter {
|
|
|
5887
5898
|
const interactions = this.convertBackendInteractionsToInteractionFormat(scenarioData.interactions || []);
|
|
5888
5899
|
// Get settings and viewport
|
|
5889
5900
|
const settings = (options === null || options === void 0 ? void 0 : options.recorderSettings) || this.getDefaultRecorderSettings();
|
|
5890
|
-
const viewPort = (options === null || options === void 0 ? void 0 : options.viewPort) || scenarioData.viewPort
|
|
5901
|
+
const viewPort = this.normalizeViewPort((options === null || options === void 0 ? void 0 : options.viewPort) || scenarioData.viewPort);
|
|
5891
5902
|
// Get parameter table rows
|
|
5892
5903
|
const rows = scenarioData.parameterTable || [];
|
|
5893
5904
|
// Generate code
|
|
@@ -6073,6 +6084,22 @@ export default class ProboReporter implements Reporter {
|
|
|
6073
6084
|
throw new Error(`Failed to lookup test suite: ${response.status} ${errorText}`);
|
|
6074
6085
|
}
|
|
6075
6086
|
const data = await response.json();
|
|
6087
|
+
// TEMPORARY DEBUG: Output the full response
|
|
6088
|
+
console.log('🔍 DEBUG: API Response URL:', url);
|
|
6089
|
+
console.log('🔍 DEBUG: API Response Status:', response.status);
|
|
6090
|
+
console.log('🔍 DEBUG: API Response Data:', JSON.stringify(data, null, 2));
|
|
6091
|
+
console.log('🔍 DEBUG: Is Array?', Array.isArray(data));
|
|
6092
|
+
console.log('🔍 DEBUG: Array length:', Array.isArray(data) ? data.length : 'N/A');
|
|
6093
|
+
console.log('🔍 DEBUG: Has results?', !!data.results);
|
|
6094
|
+
console.log('🔍 DEBUG: Results is Array?', Array.isArray(data.results));
|
|
6095
|
+
console.log('🔍 DEBUG: Results length:', Array.isArray(data.results) ? data.results.length : 'N/A');
|
|
6096
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
6097
|
+
console.log('🔍 DEBUG: First item:', JSON.stringify(data[0], null, 2));
|
|
6098
|
+
}
|
|
6099
|
+
if (data.results && Array.isArray(data.results) && data.results.length > 0) {
|
|
6100
|
+
console.log('🔍 DEBUG: First result:', JSON.stringify(data.results[0], null, 2));
|
|
6101
|
+
}
|
|
6102
|
+
console.log('');
|
|
6076
6103
|
if (Array.isArray(data) && data.length > 0) {
|
|
6077
6104
|
return data[0].id;
|
|
6078
6105
|
}
|
|
@@ -6091,7 +6118,52 @@ export default class ProboReporter implements Reporter {
|
|
|
6091
6118
|
if (codeGenResult.scenarios.length === 0) {
|
|
6092
6119
|
throw new Error(`No scenarios found in test suite ${testSuiteId}. Cannot generate test files.`);
|
|
6093
6120
|
}
|
|
6094
|
-
//
|
|
6121
|
+
// Delete everything in the test suite directory except node_modules
|
|
6122
|
+
// This ensures a clean state while preserving dependencies for faster subsequent runs
|
|
6123
|
+
if (fs__namespace.existsSync(testSuiteDir)) {
|
|
6124
|
+
try {
|
|
6125
|
+
const nodeModulesPath = path__namespace.join(testSuiteDir, 'node_modules');
|
|
6126
|
+
const hasNodeModules = fs__namespace.existsSync(nodeModulesPath);
|
|
6127
|
+
// Temporarily move node_modules out of the way if it exists
|
|
6128
|
+
let tempNodeModulesPath = null;
|
|
6129
|
+
if (hasNodeModules) {
|
|
6130
|
+
tempNodeModulesPath = path__namespace.join(testSuiteDir, '..', `node_modules.temp.${testSuiteId}`);
|
|
6131
|
+
// Remove temp directory if it exists from a previous failed run
|
|
6132
|
+
if (fs__namespace.existsSync(tempNodeModulesPath)) {
|
|
6133
|
+
fs__namespace.rmSync(tempNodeModulesPath, { recursive: true, force: true });
|
|
6134
|
+
}
|
|
6135
|
+
fs__namespace.renameSync(nodeModulesPath, tempNodeModulesPath);
|
|
6136
|
+
console.log(`📦 Preserved node_modules temporarily`);
|
|
6137
|
+
}
|
|
6138
|
+
// Delete everything in the directory
|
|
6139
|
+
const entries = fs__namespace.readdirSync(testSuiteDir, { withFileTypes: true });
|
|
6140
|
+
for (const entry of entries) {
|
|
6141
|
+
const entryPath = path__namespace.join(testSuiteDir, entry.name);
|
|
6142
|
+
try {
|
|
6143
|
+
if (entry.isDirectory()) {
|
|
6144
|
+
fs__namespace.rmSync(entryPath, { recursive: true, force: true });
|
|
6145
|
+
}
|
|
6146
|
+
else {
|
|
6147
|
+
fs__namespace.unlinkSync(entryPath);
|
|
6148
|
+
}
|
|
6149
|
+
}
|
|
6150
|
+
catch (error) {
|
|
6151
|
+
console.warn(`⚠️ Failed to delete ${entry.name}:`, error);
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
console.log(`🗑️ Cleaned test suite directory (preserved node_modules)`);
|
|
6155
|
+
// Move node_modules back
|
|
6156
|
+
if (hasNodeModules && tempNodeModulesPath) {
|
|
6157
|
+
fs__namespace.renameSync(tempNodeModulesPath, nodeModulesPath);
|
|
6158
|
+
console.log(`📦 Restored node_modules`);
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
catch (error) {
|
|
6162
|
+
console.warn(`⚠️ Failed to clean test suite directory ${testSuiteDir}:`, error);
|
|
6163
|
+
// Continue anyway - we'll overwrite files as needed
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
// Ensure directories exist (will recreate after deletion)
|
|
6095
6167
|
ensureDirectoryExists(testSuiteDir);
|
|
6096
6168
|
const testsDir = path__namespace.join(testSuiteDir, 'tests');
|
|
6097
6169
|
ensureDirectoryExists(testsDir);
|
|
@@ -6178,7 +6250,7 @@ export default class ProboReporter implements Reporter {
|
|
|
6178
6250
|
* Generates files, installs dependencies, and executes tests
|
|
6179
6251
|
*/
|
|
6180
6252
|
static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
|
|
6181
|
-
const { outputDir, includeReporter = true, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
|
|
6253
|
+
const { outputDir, includeReporter = true, playwrightArgs = [], onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
|
|
6182
6254
|
const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
|
|
6183
6255
|
let currentRunId = runId;
|
|
6184
6256
|
try {
|
|
@@ -6257,6 +6329,9 @@ export default class ProboReporter implements Reporter {
|
|
|
6257
6329
|
}
|
|
6258
6330
|
// Run Playwright tests with streaming output
|
|
6259
6331
|
console.log(`🚀 Running Playwright tests in ${testSuiteDir}...`);
|
|
6332
|
+
if (playwrightArgs.length > 0) {
|
|
6333
|
+
console.log(`🧪 Playwright extra args: ${playwrightArgs.join(' ')}`);
|
|
6334
|
+
}
|
|
6260
6335
|
return new Promise(async (resolve) => {
|
|
6261
6336
|
var _a, _b;
|
|
6262
6337
|
let stdout = '';
|
|
@@ -6273,7 +6348,7 @@ export default class ProboReporter implements Reporter {
|
|
|
6273
6348
|
let lastStepsUpdate = 0;
|
|
6274
6349
|
const collectedSteps = [];
|
|
6275
6350
|
// Use spawn for streaming output
|
|
6276
|
-
const testProcess = child_process.spawn('npx', ['playwright', 'test'], {
|
|
6351
|
+
const testProcess = child_process.spawn('npx', ['playwright', 'test', ...playwrightArgs], {
|
|
6277
6352
|
cwd: testSuiteDir,
|
|
6278
6353
|
shell: true,
|
|
6279
6354
|
stdio: ['ignore', 'pipe', 'pipe'],
|