@testsmith/testblocks 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ :root{--ts-blue: #205d96;--ts-blue-light: #e0f2fe;--ts-blue-dark: #1a4a78;--ts-green: #9fc93c;--ts-green-light: #ecfccb;--ts-green-dark: #5c7a1f;--ts-gray: #59575d;--ts-gray-light: #6b7280;--ts-gray-bg: #f8fafc;--primary-color: var(--ts-blue);--primary-dark: var(--ts-blue-dark);--primary-light: var(--ts-blue-light);--secondary-color: var(--ts-gray);--background: var(--ts-gray-bg);--surface: #ffffff;--error: #d32f2f;--success: var(--ts-green-dark);--success-light: var(--ts-green-light);--warning: #e67e00;--text-primary: var(--ts-gray);--text-secondary: var(--ts-gray-light);--border-color: #e0e0e0}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background-color:var(--background);color:var(--text-primary)}.app{display:flex;flex-direction:column;height:100vh;overflow:hidden}.header{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:56px;background:var(--primary-color);color:#fff;box-shadow:0 2px 4px #0000001a;z-index:100}.header h1{font-size:20px;font-weight:500;display:flex;align-items:center;gap:8px}.header-actions{display:flex;gap:8px}.btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-primary{background:#fff;color:var(--primary-color)}.btn-primary:hover:not(:disabled){background:#ffffffe6}.btn-secondary{background:#ffffff26;color:#fff}.btn-secondary:hover:not(:disabled){background:#ffffff40}.btn-success{background:var(--ts-green);color:#fff}.btn-success:hover:not(:disabled){background:var(--ts-green-dark)}.btn-danger{background:var(--error);color:#fff}.btn-danger:hover:not(:disabled){background:#c62828}.main-content{display:flex;flex:1;overflow:hidden}.sidebar{width:280px;background:var(--surface);border-right:1px solid var(--border-color);display:flex;flex-direction:column;overflow:hidden;transition:width .2s ease}.sidebar.collapsed{width:40px}.sidebar-toggle-header{display:flex;align-items:center;justify-content:flex-end;padding:8px;border-bottom:1px solid var(--border-color)}.sidebar.collapsed .sidebar-toggle-header{justify-content:center}.sidebar-header{padding:16px;border-bottom:1px solid var(--border-color)}.sidebar-header h2{font-size:14px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px}.test-list{flex:1;overflow-y:auto;padding:8px}.test-item{padding:12px;border-radius:4px;cursor:pointer;border:1px solid transparent;margin-bottom:4px;transition:background-color .2s}.test-item:hover{background:var(--background)}.test-item.active{background:#1976d214;border-color:var(--primary-color)}.test-item-name{font-weight:500;font-size:14px;margin-bottom:4px}.test-item-steps{font-size:12px;color:var(--text-secondary)}.add-test-btn{margin:8px;padding:12px;border:2px dashed var(--border-color);border-radius:4px;background:transparent;color:var(--text-secondary);cursor:pointer;font-size:14px;transition:border-color .2s,color .2s}.add-test-btn:hover{border-color:var(--primary-color);color:var(--primary-color)}.editor-area{flex:1;display:flex;flex-direction:column;overflow:hidden}.editor-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:var(--surface);border-bottom:1px solid var(--border-color)}.test-name-input{font-size:16px;font-weight:500;padding:8px 12px;border:1px solid var(--border-color);border-radius:4px;width:300px}.test-name-input:focus{outline:none;border-color:var(--primary-color)}.blockly-container{flex:1;position:relative}#blockly-div{position:absolute;top:0;left:0;right:0;bottom:0}.blocklyTreeSeparator{height:1px!important;margin:12px 8px!important;background-color:#0006!important;border:none!important}.blocklyWidgetDiv .blocklyHtmlInput{max-width:350px;font-family:monospace;font-size:12px;line-height:1.4}.results-panel{width:350px;background:var(--surface);border-left:1px solid var(--border-color);display:flex;flex-direction:column;overflow:hidden;transition:width .2s ease}.results-panel.collapsed{width:40px}.results-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border-color);gap:12px}.results-panel.collapsed .results-header{justify-content:center;padding:12px 8px}.panel-toggle-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:12px;padding:4px 8px;border-radius:4px;flex-shrink:0}.panel-toggle-btn:hover{background:var(--hover-bg);color:var(--text-primary)}.results-header h2{font-size:14px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;flex:1;display:flex;align-items:center;gap:8px}.results-summary{display:flex;gap:8px;font-size:12px;font-weight:500;text-transform:none}.results-summary .passed-count{color:var(--success)}.results-summary .failed-count{color:var(--danger)}.results-summary .skipped-count{color:#f59e0b}.results-actions{display:flex;gap:8px}.btn-report{padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--border-color);border-radius:4px;background:var(--surface);color:var(--text-secondary);cursor:pointer;transition:all .2s}.btn-report:hover{background:var(--primary-light);border-color:var(--primary-color);color:var(--primary-color)}.results-content{flex:1;overflow-y:auto;padding:16px}.result-item{padding:12px;border-radius:4px;margin-bottom:8px;border-left:4px solid}.result-item.passed{background:#388e3c14;border-left-color:var(--success)}.result-item.failed{background:#d32f2f14;border-left-color:var(--error)}.result-item.running{background:#1976d214;border-left-color:var(--primary-color)}.result-step{font-size:13px;margin-bottom:4px}.result-error{font-size:12px;color:var(--error);font-family:monospace;white-space:pre-wrap;margin-top:8px}.result-duration{font-size:11px;color:var(--text-secondary)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.modal{background:var(--surface);border-radius:8px;padding:24px;min-width:400px;max-width:600px;max-height:80vh;overflow-y:auto;box-shadow:0 4px 20px #00000026}.modal h2{margin-bottom:16px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:24px}.folder-hooks-modal{background:var(--surface);border-radius:8px;width:90vw;max-width:1200px;height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px #00000026}.folder-hooks-header{padding:16px 24px;border-bottom:1px solid var(--border-color);position:relative}.folder-hooks-header h2{margin:0 0 4px;font-size:18px}.folder-hooks-description{font-size:13px;color:var(--text-secondary);margin:0}.folder-hooks-tabs{display:flex;border-bottom:1px solid var(--border-color);padding:0 16px;background:var(--background)}.folder-hooks-tab{padding:12px 16px;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;display:flex;align-items:center;gap:8px}.folder-hooks-tab:hover{color:var(--text-primary)}.folder-hooks-tab.active{color:var(--primary-color);border-bottom-color:var(--primary-color)}.folder-hooks-tab .tab-badge{font-size:10px;padding:2px 6px;border-radius:10px;background:var(--primary-color);color:#fff}.folder-hooks-workspace{flex:1;min-height:0}.folder-hooks-footer{padding:16px 24px;border-top:1px solid var(--border-color);display:flex;justify-content:flex-end;gap:8px}.form-group{margin-bottom:16px}.form-group label{display:block;font-size:14px;font-weight:500;margin-bottom:6px}.form-group input,.form-group textarea{width:100%;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:14px}.form-group input:focus,.form-group textarea:focus{outline:none;border-color:var(--primary-color)}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-secondary);text-align:center;padding:40px}.empty-state h3{margin-bottom:8px;color:var(--text-primary)}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--background)}::-webkit-scrollbar-thumb{background:#bdbdbd;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#9e9e9e}.status-indicator{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}.status-indicator.running{background:var(--primary-color);animation:pulse 1s infinite}.status-indicator.passed{background:var(--success)}.status-indicator.failed{background:var(--error)}.sidebar-section{border-bottom:1px solid var(--border-color)}.sidebar-section:last-child{border-bottom:none;flex:1;display:flex;flex-direction:column;overflow:hidden}.sidebar-header.clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.sidebar-header.clickable:hover{background:var(--background)}.variables-list{padding:8px}.variable-item{display:flex;align-items:center;gap:8px;padding:6px 0}.variable-name{font-family:monospace;font-size:12px;color:var(--primary-color);min-width:80px;flex-shrink:0}.variable-value{flex:1;padding:4px 8px;border:1px solid var(--border-color);border-radius:4px;font-size:12px}.variable-value:focus{outline:none;border-color:var(--primary-color)}.global-variables{background:linear-gradient(to right,rgba(32,93,150,.05),transparent)}.variable-item.global{padding:4px 0}.variable-item.global .variable-name{color:var(--ts-blue);font-size:11px;word-break:break-all}.variable-value.readonly{background:var(--background);color:var(--text-secondary);font-family:monospace;font-size:11px;padding:2px 6px;border:none;border-radius:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.variable-value.global-input{flex:1;padding:4px 6px;border:1px solid rgba(32,93,150,.3);border-radius:4px;font-size:11px;font-family:monospace;background:#205d960d;color:var(--text-primary)}.variable-value.global-input:focus{outline:none;border-color:var(--ts-blue);background:#205d961a}.variable-group{margin-bottom:8px}.variable-group-header{font-weight:600;font-size:11px;color:var(--text-secondary);text-transform:uppercase;padding:4px 0;border-bottom:1px solid var(--border-color);margin-bottom:4px}.variable-group-content{padding-left:12px;border-left:2px solid rgba(32,93,150,.3)}.global-badge{font-size:12px;margin-left:8px;opacity:.7}.btn-icon{background:none;border:none;color:var(--text-secondary);cursor:pointer;padding:4px 8px;font-size:16px;line-height:1;border-radius:4px}.btn-icon:hover{background:var(--background);color:var(--error)}.add-variable-btn{width:100%;margin-top:8px;padding:8px;border:1px dashed var(--border-color);border-radius:4px;background:transparent;color:var(--text-secondary);cursor:pointer;font-size:12px}.add-variable-btn:hover{border-color:var(--primary-color);color:var(--primary-color)}.test-item{display:flex;align-items:center;justify-content:space-between}.test-item-content{flex:1;min-width:0}.test-item-name{display:flex;align-items:center;gap:6px}.data-driven-badge{font-size:10px;font-weight:600;background:#1565c0;color:#fff;padding:1px 5px;border-radius:8px;margin-left:4px}.btn-run-test{background:none;border:1px solid var(--border-color);border-radius:4px;padding:4px 8px;cursor:pointer;font-size:10px;color:var(--text-secondary);flex-shrink:0;margin-left:8px}.btn-run-test:hover:not(:disabled){background:var(--primary-color);border-color:var(--primary-color);color:#fff}.btn-run-test:disabled{opacity:.5;cursor:not-allowed}.status-dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex-shrink:0}.status-dot.passed{background:var(--success)}.status-dot.failed{background:var(--error)}.test-item.passed{background:#388e3c0d}.test-item.failed{background:#d32f2f0d}.test-item.passed.active{background:#388e3c1f}.test-item.failed.active{background:#d32f2f1f}.test-item.disabled{opacity:.6;background:#9e9e9e0d}.test-item.disabled.active{background:#9e9e9e1f}.test-item.disabled .btn-run-test{opacity:.4;cursor:not-allowed}.test-name-disabled{text-decoration:line-through;color:var(--text-secondary)}.status-dot.skipped{background:#f59e0b}.test-item.skipped{background:#f59e0b0d}.test-item.skipped.active{background:#f59e0b1f}.test-id-indicator{display:flex;align-items:center;gap:6px;color:#ffffffe6;font-size:13px;padding:6px 12px;border-radius:4px;background:#ffffff1a}.test-id-indicator code{font-family:Monaco,Menlo,monospace;background:#fff3;padding:2px 6px;border-radius:3px;font-size:12px}.headless-toggle{display:flex;align-items:center;gap:6px;color:#fff;font-size:14px;cursor:pointer;padding:8px 12px;border-radius:4px;background:#ffffff1a}.headless-toggle:hover{background:#fff3}.headless-toggle input[type=checkbox]{width:16px;height:16px;cursor:pointer}.create-block-modal{min-width:500px;max-width:600px}.create-block-modal h2{color:var(--text-primary);margin-bottom:20px}.helper-text{font-size:12px;color:var(--text-secondary);margin-bottom:8px}.color-picker{display:flex;gap:8px}.color-option{width:32px;height:32px;border-radius:4px;border:2px solid transparent;cursor:pointer;transition:transform .1s,border-color .1s}.color-option:hover{transform:scale(1.1)}.color-option.selected{border-color:var(--text-primary)}.param-list{max-height:200px;overflow-y:auto;border:1px solid var(--border-color);border-radius:4px;padding:8px}.empty-params{color:var(--text-secondary);font-size:13px;padding:12px;text-align:center}.param-item{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer;font-size:13px}.param-item:hover{background:var(--background)}.param-item input[type=checkbox]{flex-shrink:0}.param-name{font-weight:500;color:var(--primary-color)}.param-source{color:var(--text-secondary);font-size:11px}.param-default{color:var(--text-secondary);font-size:11px;margin-left:auto;font-family:monospace}.steps-preview{max-height:150px;overflow-y:auto;border:1px solid var(--border-color);border-radius:4px;padding:8px}.step-preview-item{display:flex;align-items:center;gap:8px;padding:4px 8px;font-size:13px}.step-number{color:var(--text-secondary);font-size:11px;width:20px}.step-type{color:var(--text-primary)}.create-block-modal .btn-primary{background:var(--primary-color);color:#fff}.create-block-modal .btn-primary:hover{background:var(--primary-dark)}.create-block-modal .btn-secondary{background:var(--background);color:var(--text-primary)}.result-test-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.result-file-name{color:var(--text-secondary);font-size:12px;margin-right:4px}.result-test-name{font-weight:500;flex:1}.result-steps{margin-top:8px;border-top:1px solid var(--border-color);padding-top:8px}.step-result-item{margin-bottom:6px;border-radius:4px;background:#00000005}.step-result-item.passed{border-left:3px solid var(--success)}.step-result-item.failed{border-left:3px solid var(--error)}.step-result-header{display:flex;align-items:center;gap:8px;padding:6px 8px;cursor:pointer;-webkit-user-select:none;user-select:none}.step-result-header:hover{background:#0000000a}.step-result-type{font-size:12px;color:var(--text-primary);white-space:nowrap}.step-result-summary{flex:1;font-size:11px;color:var(--text-secondary);font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:300px}.step-result-duration{font-size:11px;color:var(--text-secondary);white-space:nowrap}.step-result-expand{font-size:10px;color:var(--text-secondary);width:16px;text-align:center}.step-result-header.expandable{cursor:pointer}.step-result-header.expandable:hover{background:#0000000a}.step-result-details{padding:8px;background:var(--background);border-radius:0 0 4px 4px}.step-output{background:var(--surface);border-radius:4px;padding:8px}.step-result-error{padding:6px 8px;font-size:11px;color:var(--error);background:#d32f2f0d;font-family:monospace}.step-screenshot{margin-bottom:12px}.screenshot-label{font-size:11px;font-weight:600;color:var(--text-secondary);margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px}.failure-screenshot{max-width:100%;max-height:300px;border:2px solid var(--error);border-radius:6px;cursor:pointer;transition:transform .2s,box-shadow .2s;display:block}.failure-screenshot:hover{transform:scale(1.02);box-shadow:0 4px 12px #d32f2f4d}.stack-trace{font-size:10px;line-height:1.4;color:var(--error);max-height:200px;overflow-y:auto}.step-result-response{padding:8px;background:var(--background);border-radius:0 0 4px 4px}.response-status{display:flex;align-items:center;gap:8px;margin-bottom:8px}.response-label{font-size:11px;color:var(--text-secondary)}.response-status-code{font-family:monospace;font-size:12px;font-weight:600;padding:2px 6px;border-radius:3px;background:var(--surface)}.response-status-code.status-success{color:var(--success);background:#388e3c1a}.response-status-code.status-client-error{color:var(--warning);background:#f57c001a}.response-status-code.status-server-error{color:var(--error);background:#d32f2f1a}.response-section{margin-top:8px}.response-section summary{font-size:11px;color:var(--text-secondary);cursor:pointer;padding:4px 0;-webkit-user-select:none;user-select:none}.response-section summary:hover{color:var(--primary-color)}.response-pre{margin:4px 0 0;padding:8px;background:var(--surface);border:1px solid var(--border-color);border-radius:4px;font-size:11px;font-family:monospace;overflow-x:auto;max-height:200px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}.lifecycle-badge{display:inline-block;font-size:10px;font-weight:600;text-transform:uppercase;padding:2px 6px;border-radius:3px;background:var(--ts-gray);color:#fff;letter-spacing:.5px}.result-item.lifecycle,.result-item.lifecycle.passed{border-left-color:var(--ts-gray);background:#59575d14}.result-item.lifecycle.failed{border-left-color:#d32f2f;background:#d32f2f14}.editor-tabs{display:flex;background:var(--surface);border-bottom:1px solid var(--border-color);padding:0 8px}.editor-tab{padding:10px 16px;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s;display:flex;align-items:center;gap:6px}.editor-tab:hover{color:var(--text-primary);background:var(--background)}.editor-tab.active{color:var(--primary-color);border-bottom-color:var(--primary-color)}.editor-tab.test-tab{margin:0 auto 0 0;font-weight:600}.editor-tab.test-tab.active{color:var(--primary-color)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;font-size:11px;font-weight:600;background:var(--ts-gray);color:#fff;border-radius:9px}.editor-tab.active .tab-badge{background:var(--primary-color)}.lifecycle-toolbar-info{display:flex;align-items:center;gap:8px;color:var(--text-primary);font-size:14px;font-weight:500}.lifecycle-icon{font-size:16px}.lifecycle-hint{color:var(--text-secondary);font-weight:400;font-size:13px}.sidebar-tabs{display:flex;border-bottom:1px solid var(--border-color)}.sidebar-tab{flex:1;padding:10px;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s}.sidebar-tab:hover{color:var(--text-primary);background:var(--background)}.sidebar-tab.active{color:var(--primary-color);border-bottom-color:var(--primary-color)}.file-tree-section,.file-tree{flex:1;overflow:hidden;display:flex;flex-direction:column}.file-tree-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--border-color);background:var(--background)}.file-tree-root-name{font-weight:600;font-size:13px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-tree-refresh{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:16px;padding:4px;border-radius:4px}.file-tree-refresh:hover{background:var(--surface);color:var(--primary-color)}.file-tree-header-actions{display:flex;align-items:center;gap:4px}.file-tree-action-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:12px;padding:4px 6px;border-radius:4px}.file-tree-action-btn:hover{background:var(--surface);color:var(--primary-color)}.folder-actions{position:relative;margin-left:auto}.folder-action-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:14px;font-weight:700;width:20px;height:20px;border-radius:4px;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s}.file-tree-item:hover .folder-action-btn{opacity:1}.folder-action-btn:hover{background:var(--primary-light);color:var(--primary-color)}.folder-actions-menu{position:absolute;top:100%;right:0;background:#fff;border:1px solid var(--border-color);border-radius:6px;box-shadow:0 4px 12px #00000026;z-index:100;min-width:140px;padding:4px 0}.folder-actions-menu button{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:none;background:none;text-align:left;font-size:13px;color:var(--text-primary);cursor:pointer}.folder-actions-menu button:hover{background:var(--primary-light);color:var(--primary-color)}.folder-actions-menu button.delete-action:hover{background:#fee2e2;color:#dc2626}.folder-actions-menu button.run-action{color:#16a34a;font-weight:500}.folder-actions-menu button.run-action:hover{background:#dcfce7;color:#15803d}.folder-actions-menu button:disabled{opacity:.5;cursor:not-allowed}.file-tree-run-btn{color:#16a34a!important}.file-tree-run-btn:hover:not(:disabled){background:#dcfce7!important}.file-tree-run-btn:disabled{opacity:.5;cursor:not-allowed}.file-tree-content{flex:1;overflow-y:auto;padding:4px 0}.file-tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px 16px;color:var(--text-secondary);text-align:center}.file-tree-empty p{margin:4px 0;font-size:13px}.file-tree-hint{font-size:12px;opacity:.7}.file-tree-item{display:flex;align-items:center;gap:6px;padding:6px 8px;cursor:pointer;font-size:13px;color:var(--text-primary);border-left:2px solid transparent;transition:all .15s;position:relative}.file-tree-item:hover{background:var(--background)}.file-tree-item.selected{background:#1976d214;border-left-color:var(--primary-color)}.file-tree-item.folder{color:var(--text-secondary)}.file-tree-item[draggable=true]{cursor:grab}.file-tree-item[draggable=true]:active{cursor:grabbing}.file-tree-item.dragging{opacity:.5;background:var(--background)}.file-tree-item.drag-over{background:#1976d226;border-left-color:var(--primary-color);border-left-width:3px}.file-tree-item.drag-over:after{content:"";position:absolute;left:0;right:0;top:0;bottom:0;border:2px dashed var(--primary-color);border-radius:4px;pointer-events:none}.file-tree-icon{font-size:14px;flex-shrink:0}.file-tree-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.file-tree-badge{font-size:10px;font-weight:600;padding:2px 6px;border-radius:10px;background:var(--primary-color);color:#fff;flex-shrink:0}.file-tree-failed-indicator{color:#ef4444;font-size:10px;margin-left:4px;flex-shrink:0}.folder-hooks-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:12px;padding:2px 4px;opacity:0;transition:opacity .2s;flex-shrink:0}.file-tree-item.folder:hover .folder-hooks-btn{opacity:1}.folder-hooks-btn:hover{color:var(--primary-color)}.folder-hooks-indicator{font-size:10px;color:var(--primary-color);margin-left:4px;flex-shrink:0}.header-version{margin-left:8px;font-size:11px;font-weight:400;color:#ffffff80;background:#ffffff1a;padding:2px 6px;border-radius:4px}.header-file-path{margin-left:12px;font-size:12px;font-weight:400;color:#ffffffb3;max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-flex;align-items:center;gap:8px}.auto-save-indicator{font-size:11px;padding:2px 8px;border-radius:4px;flex-shrink:0}.auto-save-indicator.saving{background:#ffc10733;color:#ffc107;animation:pulse 1s ease-in-out infinite}.auto-save-indicator.saved{background:#4caf5033;color:#4caf50}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.folder-hooks-label{background:rgba(var(--ts-blue),.1);background:#e0f2fe;color:var(--ts-blue);padding:2px 8px;border-radius:4px;font-size:11px;font-weight:500;margin-right:6px}.help-modal{width:950px;max-width:95vw;height:85vh;max-height:85vh;display:flex;flex-direction:column;padding:0;overflow:hidden}.help-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border-color);flex-shrink:0}.help-header h2{margin:0;font-size:20px;font-weight:500}.btn-close{background:none;border:none;font-size:24px;cursor:pointer;color:var(--text-secondary);padding:4px 8px;border-radius:4px;line-height:1}.btn-close:hover{background:var(--background);color:var(--text-primary)}.help-layout{display:flex;flex:1;overflow:hidden}.help-nav{width:200px;flex-shrink:0;background:var(--background);border-right:1px solid var(--border-color);padding:12px 0;overflow-y:auto}.help-nav-item{display:block;width:100%;padding:10px 20px;border:none;background:none;text-align:left;cursor:pointer;font-size:14px;color:var(--text-secondary);transition:background .2s,color .2s}.help-nav-item:hover{background:#0000000a;color:var(--text-primary)}.help-nav-item.active{background:#1976d21a;color:var(--primary-color);font-weight:500;border-left:3px solid var(--primary-color);padding-left:17px}.help-content{flex:1;overflow-y:auto;padding:24px 32px}.help-content h3{margin:0 0 8px;font-size:22px;font-weight:600;color:var(--text-primary)}.help-content>p{margin:0 0 24px;color:var(--text-secondary);font-size:15px;line-height:1.5}.help-feature{margin-bottom:24px;padding:16px 20px;background:var(--background);border-radius:8px}.help-feature h4{margin:0 0 12px;font-size:15px;font-weight:600;color:var(--text-primary)}.help-feature ol,.help-feature ul{margin:0;padding-left:20px}.help-feature li{margin-bottom:8px;line-height:1.5;color:var(--text-primary)}.help-feature li:last-child{margin-bottom:0}.help-feature p{margin:0 0 12px;line-height:1.5}.help-feature code{background:#0000000f;padding:2px 6px;border-radius:4px;font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px;color:var(--primary-dark)}.help-tip{margin-bottom:24px;padding:14px 18px;background:#1976d214;border-left:3px solid var(--primary-color);border-radius:0 8px 8px 0;font-size:14px;line-height:1.5}.help-tip code{background:#0000000f;padding:2px 6px;border-radius:4px;font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px}.help-code{margin:12px 0 0;padding:12px 16px;background:#1e1e1e;color:#d4d4d4;border-radius:6px;font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px;line-height:1.5;overflow-x:auto;white-space:pre}.scan-project-btn{margin-top:8px}.scan-project-btn:disabled{opacity:.6;cursor:not-allowed}.matches-section{margin-top:12px}.matches-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;font-size:13px;color:var(--text-secondary)}.matches-actions{display:flex;gap:12px}.btn-link{background:none;border:none;color:var(--primary-color);cursor:pointer;font-size:12px;padding:0}.btn-link:hover{text-decoration:underline}.matches-list{max-height:200px;overflow-y:auto;border:1px solid var(--border-color);border-radius:6px;background:var(--surface)}.match-file-group{border-bottom:1px solid var(--border-color)}.match-file-group:last-child{border-bottom:none}.match-file-name{font-size:12px;font-weight:600;color:var(--text-primary);padding:8px 12px 4px;background:var(--background)}.match-item{display:flex;align-items:center;gap:8px;padding:6px 12px;cursor:pointer;transition:background .15s}.match-item:hover{background:#00000008}.match-item input[type=checkbox]{flex-shrink:0}.match-location{font-size:13px;color:var(--text-primary);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.match-detail{font-size:12px;color:var(--text-secondary);margin-left:4px}.record-dialog{min-width:500px;max-width:600px}.record-dialog .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--border-color)}.record-dialog .modal-header h2{margin:0;font-size:18px}.record-dialog .modal-body{min-height:150px}.record-url-input{display:flex;flex-direction:column;gap:12px}.record-url-input p{margin:0;color:var(--text-primary)}.url-input{width:100%;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:14px}.url-input:focus{outline:none;border-color:var(--primary-color)}.record-hint{font-size:13px;color:var(--text-secondary);margin-top:4px}.record-status{display:flex;align-items:center;gap:16px;padding:20px;background:var(--background);border-radius:8px;margin:16px 0}.record-status.recording{border-left:4px solid var(--error);background:#d32f2f0d}.record-status.processing{border-left:4px solid var(--warning);background:#f57c000d;justify-content:center}.recording-indicator{width:16px;height:16px;background:var(--error);border-radius:50%;animation:pulse 1.5s ease-in-out infinite;flex-shrink:0}@keyframes pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(.9)}}.recording-info h3{margin:0 0 8px;font-size:16px;color:var(--text-primary)}.recording-info p{margin:0;font-size:13px;color:var(--text-secondary)}.processing-spinner{width:24px;height:24px;border:3px solid var(--border-color);border-top-color:var(--primary-color);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.record-preview h3{margin:0 0 12px;font-size:15px}.steps-preview{max-height:300px;overflow-y:auto;border:1px solid var(--border-color);border-radius:6px;background:var(--surface)}.step-preview-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--border-color);font-size:13px}.step-preview-item:last-child{border-bottom:none}.step-number{font-weight:600;color:var(--text-secondary);min-width:24px}.step-description{color:var(--text-primary);font-family:Monaco,Menlo,Ubuntu Mono,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.record-empty{text-align:center;padding:24px;color:var(--text-secondary)}.record-empty p{margin:0 0 8px}.record-error{color:var(--error);font-size:13px;padding:8px 12px;background:#d32f2f1a;border-radius:4px}.record-error-state{text-align:center;padding:20px}.record-error-state h3{margin:0 0 12px;color:var(--error)}.record-error-state .record-error{margin-bottom:16px}.btn-warning{background:var(--warning);color:#fff}.btn-warning:hover:not(:disabled){background:#e65100}.advanced-options{margin-top:16px;border-top:1px solid var(--border-color);padding-top:12px}.advanced-toggle{display:flex;align-items:center;gap:8px;background:none;border:none;padding:8px 0;font-size:13px;color:var(--text-secondary);cursor:pointer;width:100%;text-align:left}.advanced-toggle:hover{color:var(--text-primary)}.advanced-toggle .toggle-icon{font-size:10px;width:12px}.advanced-content{padding:12px 0 4px 20px}.option-row{display:flex;align-items:center;gap:12px;margin-bottom:8px}.option-row label{font-size:13px;color:var(--text-primary);white-space:nowrap;min-width:120px}.option-input{flex:1;padding:6px 10px;border:1px solid var(--border-color);border-radius:4px;font-size:13px;font-family:Monaco,Menlo,Ubuntu Mono,monospace}.option-input:focus{outline:none;border-color:var(--primary-color)}.option-hint{font-size:12px;color:var(--text-secondary);margin:4px 0 0}.json-editor-modal{width:600px;max-width:90vw;min-height:400px;max-height:80vh;display:flex;flex-direction:column;padding:0}.json-editor-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color)}.json-editor-header h3{margin:0;font-size:16px;font-weight:600}.json-editor-toolbar{display:flex;align-items:center;gap:8px;padding:12px 20px;background:var(--background);border-bottom:1px solid var(--border-color)}.json-editor-toolbar .btn-small{padding:4px 12px;font-size:12px}.json-status{margin-left:auto;font-size:12px;font-weight:500;padding:4px 8px;border-radius:4px}.json-status.valid{color:var(--success);background:var(--success-light)}.json-status.invalid{color:var(--error);background:#fef2f2}.json-editor-content{flex:1;display:flex;flex-direction:column;padding:16px 20px;min-height:200px}.json-textarea{flex:1;width:100%;min-height:250px;padding:12px;border:1px solid var(--border-color);border-radius:4px;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace;font-size:13px;line-height:1.5;resize:vertical;background:#fafafa}.json-textarea:focus{outline:none;border-color:var(--primary-color);background:#fff}.json-textarea.has-error{border-color:var(--error);background:#fef8f8}.json-error{margin-top:8px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:4px;color:var(--error);font-size:12px;font-family:monospace}.json-editor-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid var(--border-color);background:var(--background)}.json-edit-button{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;margin-left:4px;background:var(--primary-light);border:1px solid var(--primary-color);border-radius:3px;color:var(--primary-color);font-size:11px;cursor:pointer;transition:background .2s}.json-edit-button:hover{background:var(--primary-color);color:#fff}.reopen-folder-prompt{display:flex;flex-direction:column;gap:8px;padding:12px;margin:8px;background:var(--primary-light);border:1px solid var(--primary-color);border-radius:6px;font-size:13px}.reopen-folder-prompt span{color:var(--text-secondary)}.reopen-folder-prompt strong{color:var(--text-primary);word-break:break-all}.reopen-folder-prompt .btn{align-self:flex-start}.toast-container{position:fixed;top:16px;right:16px;z-index:10000;display:flex;flex-direction:column;gap:8px;max-width:400px}.toast{display:flex;align-items:center;gap:10px;padding:12px 16px;border-radius:6px;background:#fff;box-shadow:0 4px 12px #00000026;animation:toast-slide-in .3s ease}@keyframes toast-slide-in{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.toast-icon{flex-shrink:0;width:20px;height:20px;display:flex;align-items:center;justify-content:center;border-radius:50%;font-size:12px;font-weight:700}.toast-success .toast-icon{background:var(--success-light);color:var(--success)}.toast-error .toast-icon{background:#ffebee;color:var(--error)}.toast-warning .toast-icon{background:#fff3e0;color:var(--warning)}.toast-info .toast-icon{background:var(--primary-light);color:var(--primary-color)}.toast-message{flex:1;font-size:13px;color:var(--text-primary)}.toast-dismiss{flex-shrink:0;background:none;border:none;font-size:18px;color:var(--text-secondary);cursor:pointer;padding:0;line-height:1}.toast-dismiss:hover{color:var(--text-primary)}.variables-editor{display:flex;flex-direction:column;gap:8px}.variable-editor-item{display:flex;align-items:flex-start;gap:8px;padding:8px;background:var(--background);border-radius:4px}.variable-editor-item .var-name{flex:0 0 120px;position:relative}.variable-editor-item .var-value{flex:1;min-width:0}.variable-editor-item input,.variable-editor-item textarea{width:100%;padding:6px 8px;border:1px solid var(--border-color);border-radius:4px;font-size:13px;font-family:monospace}.variable-editor-item input:focus,.variable-editor-item textarea:focus{outline:none;border-color:var(--primary-color)}.variable-editor-item .var-actions{display:flex;gap:4px}.variable-editor-item .btn-icon{padding:4px 8px;background:none;border:1px solid var(--border-color);border-radius:4px;cursor:pointer;font-size:12px}.variable-editor-item .btn-icon:hover{background:var(--border-color)}.variable-editor-item .btn-icon.delete:hover{background:#ffebee;border-color:var(--error);color:var(--error)}.variable-editor-item.duplicate{background:#fff3e0}.variable-editor-item .duplicate-warning{position:absolute;right:6px;top:50%;transform:translateY(-50%);color:#f57c00;font-size:14px;cursor:help}.add-variable-btn{align-self:flex-start;padding:6px 12px;background:var(--primary-light);border:1px dashed var(--primary-color);border-radius:4px;color:var(--primary-color);font-size:13px;cursor:pointer}.add-variable-btn:hover{background:var(--primary-color);color:#fff;border-style:solid}.variables-section-header{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border-color);margin-bottom:8px}.variables-section-header h4{margin:0;font-size:13px;font-weight:600;color:var(--text-primary)}.variables-empty{padding:16px;text-align:center;color:var(--text-secondary);font-size:13px;background:var(--background);border-radius:4px}.context-menu{position:fixed;background:#fff;border:1px solid var(--border-color);border-radius:6px;box-shadow:0 4px 12px #00000026;min-width:180px;z-index:10000;padding:4px 0}.context-menu-item{display:block;width:100%;padding:8px 12px;border:none;background:none;text-align:left;font-size:13px;color:var(--text-primary);cursor:pointer}.context-menu-item:hover:not(.disabled){background:var(--primary-light);color:var(--primary-color)}.context-menu-item.disabled{color:var(--text-secondary);cursor:not-allowed}.create-variable-dialog{background:var(--surface);border-radius:8px;min-width:400px;max-width:500px;box-shadow:0 4px 20px #00000026}.create-variable-dialog .modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color)}.create-variable-dialog .modal-header h2{margin:0;font-size:16px;font-weight:600}.create-variable-dialog .modal-close{background:none;border:none;font-size:24px;color:var(--text-secondary);cursor:pointer;padding:0;line-height:1}.create-variable-dialog .modal-close:hover{color:var(--text-primary)}.create-variable-dialog .modal-body{padding:16px 20px}.create-variable-dialog .form-group{margin-bottom:16px}.create-variable-dialog .form-group:last-child{margin-bottom:0}.create-variable-dialog .form-group label{display:block;font-size:13px;font-weight:500;margin-bottom:6px;color:var(--text-primary)}.create-variable-dialog .form-group code{background:var(--background);padding:2px 6px;border-radius:3px;font-size:12px}.create-variable-dialog .field-values-list{display:flex;flex-direction:column;gap:6px}.create-variable-dialog .field-value-option{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--border-color);border-radius:4px;cursor:pointer;font-size:13px}.create-variable-dialog .field-value-option:hover{background:var(--background)}.create-variable-dialog .field-value-option input[type=radio]{margin:0}.create-variable-dialog .field-label{font-weight:500;color:var(--text-secondary);flex-shrink:0}.create-variable-dialog .field-value{color:var(--primary-color);font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.create-variable-dialog .single-field-value{display:flex;gap:8px;padding:8px 10px;background:var(--background);border-radius:4px;font-size:13px}.create-variable-dialog .variable-type-options{display:flex;flex-direction:column;gap:8px}.create-variable-dialog .type-option{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;cursor:pointer}.create-variable-dialog .type-option:hover{background:var(--background)}.create-variable-dialog .type-option input[type=radio]{margin:0}.create-variable-dialog .type-option span:nth-child(2){font-weight:500}.create-variable-dialog .type-hint{width:100%;margin-left:22px;font-size:12px;color:var(--text-secondary)}.create-variable-dialog .form-group input[type=text]{width:100%;padding:8px 10px;border:1px solid var(--border-color);border-radius:4px;font-size:14px}.create-variable-dialog .form-group input[type=text]:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 0 2px var(--primary-light)}.create-variable-dialog .variable-preview{margin-top:6px;font-size:12px;color:var(--text-secondary)}.create-variable-dialog .variable-preview code{background:var(--success-light);color:var(--success)}.create-variable-dialog .modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid var(--border-color)}.prompt-dialog{min-width:400px;max-width:450px}.prompt-dialog .modal-header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;margin-bottom:16px;border-bottom:1px solid var(--border-color)}.prompt-dialog .modal-header h2{margin:0;font-size:18px}.prompt-dialog .modal-body{padding:0}.prompt-field{margin-bottom:16px}.prompt-field:last-child{margin-bottom:0}.prompt-field label{display:block;margin-bottom:6px;font-weight:500;color:var(--text-primary)}.prompt-field input[type=text]{width:100%;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:14px;background:var(--bg-primary);color:var(--text-primary);box-sizing:border-box}.prompt-field input[type=text]:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 0 2px var(--primary-light)}
@@ -16,8 +16,8 @@
16
16
  overflow: hidden;
17
17
  }
18
18
  </style>
19
- <script type="module" crossorigin src="/assets/index-BCKY2YTp.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-C5yUtTzz.css">
19
+ <script type="module" crossorigin src="/assets/index-BB7IxLgr.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-DTBSYs3n.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -14,7 +14,6 @@ exports.assertionBlocks = [
14
14
  tooltip: 'Assert that an element is visible (auto-waits)',
15
15
  inputs: [
16
16
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
17
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
18
17
  ],
19
18
  previousStatement: true,
20
19
  nextStatement: true,
@@ -22,7 +21,7 @@ exports.assertionBlocks = [
22
21
  const page = context.page;
23
22
  const selector = (0, utils_1.resolveSelector)(params, context);
24
23
  const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
25
- const timeout = params.TIMEOUT;
24
+ const timeout = (0, utils_1.getTimeout)(context);
26
25
  const expect = await (0, utils_1.getExpect)();
27
26
  const locator = page.locator(selector);
28
27
  context.logger.info(`Asserting ${displaySelector} is visible`);
@@ -43,7 +42,6 @@ exports.assertionBlocks = [
43
42
  tooltip: 'Assert that an element is not visible (auto-waits)',
44
43
  inputs: [
45
44
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
46
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
47
45
  ],
48
46
  previousStatement: true,
49
47
  nextStatement: true,
@@ -51,7 +49,7 @@ exports.assertionBlocks = [
51
49
  const page = context.page;
52
50
  const selector = (0, utils_1.resolveSelector)(params, context);
53
51
  const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
54
- const timeout = params.TIMEOUT;
52
+ const timeout = (0, utils_1.getTimeout)(context);
55
53
  const expect = await (0, utils_1.getExpect)();
56
54
  const locator = page.locator(selector);
57
55
  context.logger.info(`Asserting ${displaySelector} is not visible`);
@@ -73,7 +71,6 @@ exports.assertionBlocks = [
73
71
  inputs: [
74
72
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
75
73
  { name: 'TEXT', type: 'field', fieldType: 'text', required: true },
76
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
77
74
  ],
78
75
  previousStatement: true,
79
76
  nextStatement: true,
@@ -82,7 +79,7 @@ exports.assertionBlocks = [
82
79
  const selector = (0, utils_1.resolveSelector)(params, context);
83
80
  const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
84
81
  const expectedText = (0, utils_1.resolveVariables)(params.TEXT, context);
85
- const timeout = params.TIMEOUT;
82
+ const timeout = (0, utils_1.getTimeout)(context);
86
83
  const expect = await (0, utils_1.getExpect)();
87
84
  const locator = page.locator(selector);
88
85
  context.logger.info(`Asserting ${displaySelector} contains "${expectedText}"`);
@@ -104,7 +101,6 @@ exports.assertionBlocks = [
104
101
  inputs: [
105
102
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
106
103
  { name: 'TEXT', type: 'field', fieldType: 'text', required: true },
107
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
108
104
  ],
109
105
  previousStatement: true,
110
106
  nextStatement: true,
@@ -113,7 +109,7 @@ exports.assertionBlocks = [
113
109
  const selector = (0, utils_1.resolveSelector)(params, context);
114
110
  const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
115
111
  const expectedText = (0, utils_1.resolveVariables)(params.TEXT, context);
116
- const timeout = params.TIMEOUT;
112
+ const timeout = (0, utils_1.getTimeout)(context);
117
113
  const expect = await (0, utils_1.getExpect)();
118
114
  const locator = page.locator(selector);
119
115
  context.logger.info(`Asserting ${displaySelector} text equals "${expectedText}"`);
@@ -134,14 +130,13 @@ exports.assertionBlocks = [
134
130
  tooltip: 'Assert that current URL contains expected value (auto-waits)',
135
131
  inputs: [
136
132
  { name: 'TEXT', type: 'field', fieldType: 'text', required: true },
137
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
138
133
  ],
139
134
  previousStatement: true,
140
135
  nextStatement: true,
141
136
  execute: async (params, context) => {
142
137
  const page = context.page;
143
138
  const expectedText = (0, utils_1.resolveVariables)(params.TEXT, context);
144
- const timeout = params.TIMEOUT;
139
+ const timeout = (0, utils_1.getTimeout)(context);
145
140
  const expect = await (0, utils_1.getExpect)();
146
141
  // Escape special regex characters and create a regex pattern
147
142
  const escapedText = expectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -164,14 +159,13 @@ exports.assertionBlocks = [
164
159
  tooltip: 'Assert that page title contains expected value (auto-waits)',
165
160
  inputs: [
166
161
  { name: 'TEXT', type: 'field', fieldType: 'text', required: true },
167
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
168
162
  ],
169
163
  previousStatement: true,
170
164
  nextStatement: true,
171
165
  execute: async (params, context) => {
172
166
  const page = context.page;
173
167
  const expectedText = (0, utils_1.resolveVariables)(params.TEXT, context);
174
- const timeout = params.TIMEOUT;
168
+ const timeout = (0, utils_1.getTimeout)(context);
175
169
  const expect = await (0, utils_1.getExpect)();
176
170
  // Escape special regex characters and create a regex pattern
177
171
  const escapedText = expectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -193,7 +187,6 @@ exports.assertionBlocks = [
193
187
  tooltip: 'Assert that an element is enabled (auto-waits)',
194
188
  inputs: [
195
189
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
196
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
197
190
  ],
198
191
  previousStatement: true,
199
192
  nextStatement: true,
@@ -201,7 +194,7 @@ exports.assertionBlocks = [
201
194
  const page = context.page;
202
195
  const selector = (0, utils_1.resolveSelector)(params, context);
203
196
  const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
204
- const timeout = params.TIMEOUT;
197
+ const timeout = (0, utils_1.getTimeout)(context);
205
198
  const expect = await (0, utils_1.getExpect)();
206
199
  const locator = page.locator(selector);
207
200
  context.logger.info(`Asserting ${displaySelector} is enabled`);
@@ -223,7 +216,6 @@ exports.assertionBlocks = [
223
216
  inputs: [
224
217
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
225
218
  { name: 'EXPECTED', type: 'field', fieldType: 'checkbox', default: true },
226
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
227
219
  ],
228
220
  previousStatement: true,
229
221
  nextStatement: true,
@@ -232,7 +224,7 @@ exports.assertionBlocks = [
232
224
  const selector = (0, utils_1.resolveSelector)(params, context);
233
225
  const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
234
226
  const expected = params.EXPECTED;
235
- const timeout = params.TIMEOUT;
227
+ const timeout = (0, utils_1.getTimeout)(context);
236
228
  const expect = await (0, utils_1.getExpect)();
237
229
  const locator = page.locator(selector);
238
230
  context.logger.info(`Asserting ${displaySelector} is ${expected ? 'checked' : 'unchecked'}`);
@@ -261,7 +253,6 @@ exports.assertionBlocks = [
261
253
  inputs: [
262
254
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
263
255
  { name: 'VALUE', type: 'field', fieldType: 'text', required: true },
264
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
265
256
  ],
266
257
  previousStatement: true,
267
258
  nextStatement: true,
@@ -270,7 +261,7 @@ exports.assertionBlocks = [
270
261
  const selector = (0, utils_1.resolveSelector)(params, context);
271
262
  const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
272
263
  const expectedValue = (0, utils_1.resolveVariables)(params.VALUE, context);
273
- const timeout = params.TIMEOUT;
264
+ const timeout = (0, utils_1.getTimeout)(context);
274
265
  const expect = await (0, utils_1.getExpect)();
275
266
  const locator = page.locator(selector);
276
267
  context.logger.info(`Asserting ${displaySelector} has value "${expectedValue}"`);
@@ -294,7 +285,6 @@ exports.assertionBlocks = [
294
285
  inputs: [
295
286
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
296
287
  { name: 'VALUE', type: 'field', fieldType: 'text', required: true },
297
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
298
288
  ],
299
289
  previousStatement: true,
300
290
  nextStatement: true,
@@ -303,7 +293,7 @@ exports.assertionBlocks = [
303
293
  const selector = (0, utils_1.resolveSelector)(params, context);
304
294
  const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
305
295
  const expectedValue = (0, utils_1.resolveVariables)(params.VALUE, context);
306
- const timeout = params.TIMEOUT;
296
+ const timeout = (0, utils_1.getTimeout)(context);
307
297
  const expect = await (0, utils_1.getExpect)();
308
298
  const locator = page.locator(selector);
309
299
  context.logger.info(`Asserting ${displaySelector} value contains "${expectedValue}"`);
@@ -14,14 +14,13 @@ exports.interactionBlocks = [
14
14
  tooltip: 'Click on an element (auto-waits for element to be visible and stable)',
15
15
  inputs: [
16
16
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
17
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
18
17
  ],
19
18
  previousStatement: true,
20
19
  nextStatement: true,
21
20
  execute: async (params, context) => {
22
21
  const page = context.page;
23
22
  const selector = (0, utils_1.resolveSelector)(params, context);
24
- const timeout = params.TIMEOUT;
23
+ const timeout = (0, utils_1.getTimeout)(context);
25
24
  context.logger.info(`Clicking: ${selector}`);
26
25
  const locator = page.locator(selector);
27
26
  // Wait for element to be visible and stable before clicking
@@ -45,7 +44,6 @@ exports.interactionBlocks = [
45
44
  inputs: [
46
45
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
47
46
  { name: 'VALUE', type: 'field', fieldType: 'text', required: true },
48
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
49
47
  ],
50
48
  previousStatement: true,
51
49
  nextStatement: true,
@@ -54,7 +52,7 @@ exports.interactionBlocks = [
54
52
  const selector = (0, utils_1.resolveSelector)(params, context);
55
53
  const rawValue = params.VALUE;
56
54
  const value = (0, utils_1.resolveVariables)(rawValue, context);
57
- const timeout = params.TIMEOUT;
55
+ const timeout = (0, utils_1.getTimeout)(context);
58
56
  context.logger.info(`Filling ${selector} with "${value}"`);
59
57
  const locator = page.locator(selector);
60
58
  await locator.fill(value, { timeout });
@@ -76,7 +74,6 @@ exports.interactionBlocks = [
76
74
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
77
75
  { name: 'TEXT', type: 'field', fieldType: 'text', required: true },
78
76
  { name: 'DELAY', type: 'field', fieldType: 'number', default: 50 },
79
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
80
77
  ],
81
78
  previousStatement: true,
82
79
  nextStatement: true,
@@ -85,7 +82,7 @@ exports.interactionBlocks = [
85
82
  const selector = (0, utils_1.resolveSelector)(params, context);
86
83
  const text = (0, utils_1.resolveVariables)(params.TEXT, context);
87
84
  const delay = params.DELAY;
88
- const timeout = params.TIMEOUT;
85
+ const timeout = (0, utils_1.getTimeout)(context);
89
86
  context.logger.info(`Typing "${text}" into ${selector}`);
90
87
  const locator = page.locator(selector);
91
88
  await locator.pressSequentially(text, { delay, timeout });
@@ -107,7 +104,6 @@ exports.interactionBlocks = [
107
104
  inputs: [
108
105
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
109
106
  { name: 'KEY', type: 'field', fieldType: 'dropdown', options: [['Enter', 'Enter'], ['Tab', 'Tab'], ['Escape', 'Escape'], ['Backspace', 'Backspace'], ['ArrowUp', 'ArrowUp'], ['ArrowDown', 'ArrowDown'], ['ArrowLeft', 'ArrowLeft'], ['ArrowRight', 'ArrowRight']] },
110
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
111
107
  ],
112
108
  previousStatement: true,
113
109
  nextStatement: true,
@@ -115,7 +111,7 @@ exports.interactionBlocks = [
115
111
  const page = context.page;
116
112
  const selector = (0, utils_1.resolveSelector)(params, context);
117
113
  const key = params.KEY;
118
- const timeout = params.TIMEOUT;
114
+ const timeout = (0, utils_1.getTimeout)(context);
119
115
  context.logger.info(`Pressing ${key} on ${selector}`);
120
116
  const locator = page.locator(selector);
121
117
  await locator.press(key, { timeout });
@@ -135,7 +131,6 @@ exports.interactionBlocks = [
135
131
  inputs: [
136
132
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
137
133
  { name: 'VALUE', type: 'field', fieldType: 'text', required: true },
138
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
139
134
  ],
140
135
  previousStatement: true,
141
136
  nextStatement: true,
@@ -143,7 +138,7 @@ exports.interactionBlocks = [
143
138
  const page = context.page;
144
139
  const selector = (0, utils_1.resolveSelector)(params, context);
145
140
  const value = (0, utils_1.resolveVariables)(params.VALUE, context);
146
- const timeout = params.TIMEOUT;
141
+ const timeout = (0, utils_1.getTimeout)(context);
147
142
  context.logger.info(`Selecting "${value}" in ${selector}`);
148
143
  const locator = page.locator(selector);
149
144
  await locator.selectOption(value, { timeout });
@@ -163,7 +158,6 @@ exports.interactionBlocks = [
163
158
  inputs: [
164
159
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
165
160
  { name: 'ACTION', type: 'field', fieldType: 'dropdown', options: [['Check', 'check'], ['Uncheck', 'uncheck']] },
166
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
167
161
  ],
168
162
  previousStatement: true,
169
163
  nextStatement: true,
@@ -171,7 +165,7 @@ exports.interactionBlocks = [
171
165
  const page = context.page;
172
166
  const selector = (0, utils_1.resolveSelector)(params, context);
173
167
  const action = params.ACTION;
174
- const timeout = params.TIMEOUT;
168
+ const timeout = (0, utils_1.getTimeout)(context);
175
169
  context.logger.info(`${action === 'check' ? 'Checking' : 'Unchecking'} ${selector}`);
176
170
  const locator = page.locator(selector);
177
171
  if (action === 'check') {
@@ -195,14 +189,13 @@ exports.interactionBlocks = [
195
189
  tooltip: 'Hover over an element (auto-waits)',
196
190
  inputs: [
197
191
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
198
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
199
192
  ],
200
193
  previousStatement: true,
201
194
  nextStatement: true,
202
195
  execute: async (params, context) => {
203
196
  const page = context.page;
204
197
  const selector = (0, utils_1.resolveSelector)(params, context);
205
- const timeout = params.TIMEOUT;
198
+ const timeout = (0, utils_1.getTimeout)(context);
206
199
  context.logger.info(`Hovering over ${selector}`);
207
200
  const locator = page.locator(selector);
208
201
  await locator.hover({ timeout });
@@ -40,7 +40,6 @@ exports.navigationBlocks = [
40
40
  inputs: [
41
41
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
42
42
  { name: 'STATE', type: 'field', fieldType: 'dropdown', options: [['Visible', 'visible'], ['Hidden', 'hidden'], ['Attached', 'attached'], ['Detached', 'detached']] },
43
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
44
43
  ],
45
44
  previousStatement: true,
46
45
  nextStatement: true,
@@ -48,7 +47,7 @@ exports.navigationBlocks = [
48
47
  const page = context.page;
49
48
  const selector = (0, utils_1.resolveSelector)(params, context);
50
49
  const state = params.STATE;
51
- const timeout = params.TIMEOUT;
50
+ const timeout = (0, utils_1.getTimeout)(context);
52
51
  context.logger.info(`Waiting for ${selector} to be ${state}`);
53
52
  await page.waitForSelector(selector, { state, timeout });
54
53
  return {
@@ -66,14 +65,13 @@ exports.navigationBlocks = [
66
65
  tooltip: 'Wait for URL to match',
67
66
  inputs: [
68
67
  { name: 'URL', type: 'field', fieldType: 'text', required: true },
69
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
70
68
  ],
71
69
  previousStatement: true,
72
70
  nextStatement: true,
73
71
  execute: async (params, context) => {
74
72
  const page = context.page;
75
73
  const url = (0, utils_1.resolveVariables)(params.URL, context);
76
- const timeout = params.TIMEOUT;
74
+ const timeout = (0, utils_1.getTimeout)(context);
77
75
  context.logger.info(`Waiting for URL to match: ${url}`);
78
76
  await page.waitForURL(url, { timeout });
79
77
  return {
@@ -14,13 +14,12 @@ exports.retrievalBlocks = [
14
14
  tooltip: 'Get text content of an element (auto-waits)',
15
15
  inputs: [
16
16
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
17
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
18
17
  ],
19
18
  output: { type: 'String' },
20
19
  execute: async (params, context) => {
21
20
  const page = context.page;
22
21
  const selector = (0, utils_1.resolveSelector)(params, context);
23
- const timeout = params.TIMEOUT;
22
+ const timeout = (0, utils_1.getTimeout)(context);
24
23
  const locator = page.locator(selector);
25
24
  await locator.waitFor({ state: 'visible', timeout });
26
25
  const text = await locator.textContent({ timeout });
@@ -43,14 +42,13 @@ exports.retrievalBlocks = [
43
42
  inputs: [
44
43
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
45
44
  { name: 'ATTRIBUTE', type: 'field', fieldType: 'text', required: true },
46
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
47
45
  ],
48
46
  output: { type: 'String' },
49
47
  execute: async (params, context) => {
50
48
  const page = context.page;
51
49
  const selector = (0, utils_1.resolveSelector)(params, context);
52
50
  const attribute = params.ATTRIBUTE;
53
- const timeout = params.TIMEOUT;
51
+ const timeout = (0, utils_1.getTimeout)(context);
54
52
  const locator = page.locator(selector);
55
53
  await locator.waitFor({ state: 'attached', timeout });
56
54
  const value = await locator.getAttribute(attribute, { timeout });
@@ -72,13 +70,12 @@ exports.retrievalBlocks = [
72
70
  tooltip: 'Get current value of an input field (auto-waits)',
73
71
  inputs: [
74
72
  { name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
75
- { name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
76
73
  ],
77
74
  output: { type: 'String' },
78
75
  execute: async (params, context) => {
79
76
  const page = context.page;
80
77
  const selector = (0, utils_1.resolveSelector)(params, context);
81
- const timeout = params.TIMEOUT;
78
+ const timeout = (0, utils_1.getTimeout)(context);
82
79
  const locator = page.locator(selector);
83
80
  await locator.waitFor({ state: 'visible', timeout });
84
81
  const value = await locator.inputValue({ timeout });
@@ -1,4 +1,8 @@
1
1
  import { ExecutionContext } from '../../types';
2
+ /**
3
+ * Get the global web timeout from context
4
+ */
5
+ export declare function getTimeout(context: ExecutionContext): number;
2
6
  export declare function getExpect(): Promise<any>;
3
7
  /**
4
8
  * Execute a web assertion with soft assertion support
@@ -33,11 +33,19 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getTimeout = getTimeout;
36
37
  exports.getExpect = getExpect;
37
38
  exports.executeWebAssertion = executeWebAssertion;
38
39
  exports.resolveVariables = resolveVariables;
39
40
  exports.resolveSelector = resolveSelector;
40
41
  exports.getDisplaySelector = getDisplaySelector;
42
+ const DEFAULT_WEB_TIMEOUT = 30000;
43
+ /**
44
+ * Get the global web timeout from context
45
+ */
46
+ function getTimeout(context) {
47
+ return context.webTimeout ?? DEFAULT_WEB_TIMEOUT;
48
+ }
41
49
  // Import Playwright's expect for assertions with auto-waiting
42
50
  // We use dynamic import with string concatenation to prevent Vite from
43
51
  // trying to bundle the playwright package (which is Node.js-only)
@@ -106,6 +106,7 @@ export interface ExecutionContext {
106
106
  testIdAttribute?: string;
107
107
  softAssertions?: boolean;
108
108
  softAssertionErrors?: SoftAssertionError[];
109
+ webTimeout?: number;
109
110
  }
110
111
  export interface Logger {
111
112
  info(message: string, data?: unknown): void;
@@ -121,7 +121,10 @@ class TestExecutor {
121
121
  this.browser = await playwright_1.chromium.launch({
122
122
  headless: this.options.headless,
123
123
  });
124
- this.context = await this.browser.newContext();
124
+ // Use consistent viewport size for headless and headed modes
125
+ this.context = await this.browser.newContext({
126
+ viewport: { width: 1920, height: 1080 },
127
+ });
125
128
  this.page = await this.context.newPage();
126
129
  if (this.options.timeout) {
127
130
  this.page.setDefaultTimeout(this.options.timeout);
@@ -196,6 +199,7 @@ class TestExecutor {
196
199
  plugins: this.plugins,
197
200
  testIdAttribute: this.options.testIdAttribute,
198
201
  procedures: mergedProcedures,
202
+ webTimeout: this.options.timeout,
199
203
  };
200
204
  // Check if there are any enabled tests
201
205
  const enabledTests = testFile.tests.filter(t => !t.disabled);
@@ -426,6 +430,7 @@ class TestExecutor {
426
430
  // Enable soft assertions if configured on the test
427
431
  softAssertions: test.softAssertions || false,
428
432
  softAssertionErrors: [],
433
+ webTimeout: this.options.timeout,
429
434
  };
430
435
  // Run beforeTest hooks
431
436
  for (const plugin of this.plugins.values()) {
@@ -517,6 +522,7 @@ class TestExecutor {
517
522
  // Enable soft assertions if configured on the test
518
523
  softAssertions: test.softAssertions || false,
519
524
  softAssertionErrors: [],
525
+ webTimeout: this.options.timeout,
520
526
  };
521
527
  // Inject data values into variables
522
528
  for (const [key, value] of Object.entries(dataSet.values)) {
@@ -56,6 +56,10 @@ export declare function getGlobalProcedures(): Record<string, ProcedureDefinitio
56
56
  * Get the configured test ID attribute (defaults to 'data-testid')
57
57
  */
58
58
  export declare function getTestIdAttribute(): string;
59
+ /**
60
+ * Get the configured global timeout in milliseconds (defaults to 30000)
61
+ */
62
+ export declare function getGlobalTimeout(): number;
59
63
  /**
60
64
  * Set the test ID attribute and persist to globals.json
61
65
  */
@@ -47,6 +47,7 @@ exports.getGlobals = getGlobals;
47
47
  exports.getGlobalVariables = getGlobalVariables;
48
48
  exports.getGlobalProcedures = getGlobalProcedures;
49
49
  exports.getTestIdAttribute = getTestIdAttribute;
50
+ exports.getGlobalTimeout = getGlobalTimeout;
50
51
  exports.setTestIdAttribute = setTestIdAttribute;
51
52
  exports.discoverSnippets = discoverSnippets;
52
53
  exports.loadSnippet = loadSnippet;
@@ -118,6 +119,12 @@ function getGlobalProcedures() {
118
119
  function getTestIdAttribute() {
119
120
  return loadedGlobals.testIdAttribute || 'data-testid';
120
121
  }
122
+ /**
123
+ * Get the configured global timeout in milliseconds (defaults to 30000)
124
+ */
125
+ function getGlobalTimeout() {
126
+ return loadedGlobals.timeout || 30000;
127
+ }
121
128
  /**
122
129
  * Set the test ID attribute and persist to globals.json
123
130
  */
@@ -384,7 +384,8 @@ app.post('/api/reports/html', (req, res) => {
384
384
  summary: {
385
385
  totalTests: results.length,
386
386
  passed: results.filter(r => r.status === 'passed').length,
387
- failed: results.filter(r => r.status !== 'passed').length,
387
+ failed: results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length,
388
+ skipped: results.filter(r => r.status === 'skipped').length,
388
389
  duration: results.reduce((sum, r) => sum + r.duration, 0),
389
390
  },
390
391
  testFiles: [{
@@ -420,7 +421,8 @@ app.post('/api/reports/junit', (req, res) => {
420
421
  summary: {
421
422
  totalTests: results.length,
422
423
  passed: results.filter(r => r.status === 'passed').length,
423
- failed: results.filter(r => r.status !== 'passed').length,
424
+ failed: results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length,
425
+ skipped: results.filter(r => r.status === 'skipped').length,
424
426
  duration: results.reduce((sum, r) => sum + r.duration, 0),
425
427
  },
426
428
  testFiles: [{
@@ -244,7 +244,7 @@ async function startServer(options = {}) {
244
244
  const testIdAttr = (0, globals_1.getTestIdAttribute)();
245
245
  const executor = new executor_1.TestExecutor({
246
246
  headless: req.query.headless !== 'false',
247
- timeout: Number(req.query.timeout) || 30000,
247
+ timeout: Number(req.query.timeout) || (0, globals_1.getGlobalTimeout)(),
248
248
  variables: globalVars,
249
249
  procedures: globalProcs,
250
250
  testIdAttribute: testIdAttr,
@@ -294,7 +294,7 @@ async function startServer(options = {}) {
294
294
  const testIdAttr = (0, globals_1.getTestIdAttribute)();
295
295
  const executor = new executor_1.TestExecutor({
296
296
  headless: req.query.headless !== 'false',
297
- timeout: Number(req.query.timeout) || 30000,
297
+ timeout: Number(req.query.timeout) || (0, globals_1.getGlobalTimeout)(),
298
298
  variables: globalVars,
299
299
  procedures: globalProcs,
300
300
  testIdAttribute: testIdAttr,
@@ -429,7 +429,8 @@ async function startServer(options = {}) {
429
429
  summary: {
430
430
  totalTests: results.length,
431
431
  passed: results.filter(r => r.status === 'passed').length,
432
- failed: results.filter(r => r.status !== 'passed').length,
432
+ failed: results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length,
433
+ skipped: results.filter(r => r.status === 'skipped').length,
433
434
  duration: results.reduce((sum, r) => sum + r.duration, 0),
434
435
  },
435
436
  testFiles: [{
@@ -464,7 +465,8 @@ async function startServer(options = {}) {
464
465
  summary: {
465
466
  totalTests: results.length,
466
467
  passed: results.filter(r => r.status === 'passed').length,
467
- failed: results.filter(r => r.status !== 'passed').length,
468
+ failed: results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length,
469
+ skipped: results.filter(r => r.status === 'skipped').length,
468
470
  duration: results.reduce((sum, r) => sum + r.duration, 0),
469
471
  },
470
472
  testFiles: [{