skopix 2.0.29 → 2.0.31
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/package.json +1 -1
- package/web/app/index.html +56 -53
package/package.json
CHANGED
package/web/app/index.html
CHANGED
|
@@ -1620,27 +1620,25 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
1620
1620
|
<div class="modal-body" style="display:flex;flex-direction:column;gap:14px">
|
|
1621
1621
|
<input type="hidden" id="lib-step-id">
|
|
1622
1622
|
<div class="form-field">
|
|
1623
|
-
<label class="form-label">
|
|
1624
|
-
<input class="form-input" id="lib-step-name" type="text" placeholder="e.g.
|
|
1623
|
+
<label class="form-label">Element name</label>
|
|
1624
|
+
<input class="form-input" id="lib-step-name" type="text" placeholder="e.g. Chart settings icon, Username input">
|
|
1625
1625
|
</div>
|
|
1626
1626
|
<div class="form-field">
|
|
1627
|
-
<label class="form-label">
|
|
1627
|
+
<label class="form-label">Selector</label>
|
|
1628
|
+
<input class="form-input" id="lib-step-selector" type="text" placeholder="CSS selector" style="font-family:var(--mono)">
|
|
1629
|
+
<div class="form-help">The CSS selector that identifies this element</div>
|
|
1630
|
+
</div>
|
|
1631
|
+
<div class="form-field">
|
|
1632
|
+
<label class="form-label">Default action (optional)</label>
|
|
1628
1633
|
<select class="form-select" id="lib-step-action">
|
|
1634
|
+
<option value="">— No default —</option>
|
|
1629
1635
|
<option value="click">click</option>
|
|
1630
1636
|
<option value="type">type</option>
|
|
1631
1637
|
<option value="check">check</option>
|
|
1632
1638
|
<option value="assert">assert</option>
|
|
1633
1639
|
<option value="select">select</option>
|
|
1634
|
-
<option value="scroll">scroll</option>
|
|
1635
1640
|
</select>
|
|
1636
|
-
|
|
1637
|
-
<div class="form-field">
|
|
1638
|
-
<label class="form-label">Selector</label>
|
|
1639
|
-
<input class="form-input" id="lib-step-selector" type="text" placeholder="CSS selector" style="font-family:var(--mono)">
|
|
1640
|
-
</div>
|
|
1641
|
-
<div class="form-field" id="lib-step-value-row">
|
|
1642
|
-
<label class="form-label">Value (for type/assert)</label>
|
|
1643
|
-
<input class="form-input" id="lib-step-value" type="text" placeholder="Value to type or assert">
|
|
1641
|
+
<div class="form-help">Suggested action when used in the test builder — can be overridden</div>
|
|
1644
1642
|
</div>
|
|
1645
1643
|
<div class="form-field">
|
|
1646
1644
|
<label class="form-label">Tags (comma separated)</label>
|
|
@@ -5583,14 +5581,14 @@ function renderLibrarySteps(steps) {
|
|
|
5583
5581
|
Delete selected
|
|
5584
5582
|
</button>
|
|
5585
5583
|
</div>
|
|
5586
|
-
${fragileCount > 0 ? `<div style="font-family:var(--mono);font-size:11px;color:#f59e0b">⚠ ${fragileCount}
|
|
5584
|
+
${fragileCount > 0 ? `<div style="font-family:var(--mono);font-size:11px;color:#f59e0b">⚠ ${fragileCount} element${fragileCount>1?'s':''} with fragile selectors</div>` : '<div style="font-family:var(--mono);font-size:11px;color:#34d399">✓ All selectors look stable</div>'}
|
|
5587
5585
|
</div>
|
|
5588
5586
|
<table style="width:100%;border-collapse:collapse">
|
|
5589
5587
|
<thead><tr style="font-family:var(--mono);font-size:10px;color:var(--muted);letter-spacing:0.1em">
|
|
5590
5588
|
<th style="padding:10px 16px;width:32px;border-bottom:1px solid var(--border)"></th>
|
|
5591
|
-
<th style="padding:10px 20px;text-align:left;border-bottom:1px solid var(--border)">NAME</th>
|
|
5592
|
-
<th style="padding:10px 16px;text-align:left;border-bottom:1px solid var(--border)">ACTION</th>
|
|
5589
|
+
<th style="padding:10px 20px;text-align:left;border-bottom:1px solid var(--border)">ELEMENT NAME</th>
|
|
5593
5590
|
<th style="padding:10px 16px;text-align:left;border-bottom:1px solid var(--border)">SELECTOR</th>
|
|
5591
|
+
<th style="padding:10px 16px;text-align:left;border-bottom:1px solid var(--border)">DEFAULT ACTION</th>
|
|
5594
5592
|
<th style="padding:10px 16px;text-align:left;border-bottom:1px solid var(--border)">USES</th>
|
|
5595
5593
|
<th style="padding:10px 16px;text-align:right;border-bottom:1px solid var(--border)"></th>
|
|
5596
5594
|
</tr></thead>
|
|
@@ -5599,23 +5597,28 @@ function renderLibrarySteps(steps) {
|
|
|
5599
5597
|
const sel = s.stableSelector || s.selector || '';
|
|
5600
5598
|
const fragile = isSelectorFragile(sel);
|
|
5601
5599
|
const warning = getSelectorWarning(sel);
|
|
5600
|
+
const actionColors = { click:'#22d3ee', type:'#a78bfa', check:'#34d399', assert:'#fb923c', select:'#f472b6', scroll:'#6b7280' };
|
|
5602
5601
|
return `
|
|
5603
5602
|
<tr data-step-id="${escapeAttr(s.id)}" style="border-bottom:1px solid var(--border);transition:background 0.15s${fragile?';background:rgba(245,158,11,0.04)':''}" onmouseover="this.style.background='var(--surface2)'" onmouseout="this.style.background='${fragile?'rgba(245,158,11,0.04)':''}'">
|
|
5604
5603
|
<td style="padding:12px 16px">
|
|
5605
5604
|
<input type="checkbox" class="lib-step-checkbox" data-id="${escapeAttr(s.id)}" onchange="onLibStepCheck()" style="width:14px;height:14px">
|
|
5606
5605
|
</td>
|
|
5607
5606
|
<td style="padding:12px 20px">
|
|
5608
|
-
<div style="font-size:13px;color:var(--text)">${escapeHtml(s.name
|
|
5607
|
+
<div style="font-size:13px;color:var(--text)">${escapeHtml(s.name||'')}</div>
|
|
5609
5608
|
${s.tags && s.tags.length ? `<div style="margin-top:4px;display:flex;gap:4px">${s.tags.map(t=>`<span style="font-family:var(--mono);font-size:10px;padding:2px 6px;background:var(--surface2);border-radius:4px;color:var(--muted)">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
|
|
5610
5609
|
</td>
|
|
5611
|
-
<td style="padding:12px 16px
|
|
5612
|
-
<td style="padding:12px 16px;max-width:280px">
|
|
5610
|
+
<td style="padding:12px 16px;max-width:300px">
|
|
5613
5611
|
<div style="display:flex;align-items:center;gap:6px">
|
|
5614
5612
|
${fragile ? `<span title="${escapeAttr(warning||'')}" style="color:#f59e0b;font-size:14px;cursor:help;flex-shrink:0">⚠</span>` : ''}
|
|
5615
|
-
<code style="font-size:11px;color:${fragile?'#f59e0b':'var(--muted)'};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block">${escapeHtml(sel
|
|
5613
|
+
<code style="font-size:11px;color:${fragile?'#f59e0b':'var(--muted)'};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block">${escapeHtml(sel||'—')}</code>
|
|
5616
5614
|
</div>
|
|
5617
5615
|
</td>
|
|
5618
|
-
<td style="padding:12px 16px
|
|
5616
|
+
<td style="padding:12px 16px">
|
|
5617
|
+
<span style="font-family:var(--mono);font-size:11px;color:${actionColors[s.defaultAction]||'var(--muted2)'}">
|
|
5618
|
+
${s.defaultAction||'—'}
|
|
5619
|
+
</span>
|
|
5620
|
+
</td>
|
|
5621
|
+
<td style="padding:12px 16px;font-family:var(--mono);font-size:12px;color:var(--muted)">${s.usageCount||0}</td>
|
|
5619
5622
|
<td style="padding:12px 16px;text-align:right">
|
|
5620
5623
|
<button class="btn btn-ghost" style="padding:4px 10px;font-size:11px;margin-right:4px" onclick="editLibraryStep('${escapeAttr(s.id)}')">Edit</button>
|
|
5621
5624
|
<button class="btn btn-ghost" style="padding:4px 10px;font-size:11px;color:var(--red)" onclick="deleteLibraryStepUI('${escapeAttr(s.id)}','${escapeAttr(s.name||'')}')">Delete</button>
|
|
@@ -5730,11 +5733,10 @@ async function loadLibraryView() {
|
|
|
5730
5733
|
function openAddLibraryStep() {
|
|
5731
5734
|
document.getElementById('lib-step-id').value = '';
|
|
5732
5735
|
document.getElementById('lib-step-name').value = '';
|
|
5733
|
-
document.getElementById('lib-step-action').value = 'click';
|
|
5734
5736
|
document.getElementById('lib-step-selector').value = '';
|
|
5735
|
-
document.getElementById('lib-step-
|
|
5737
|
+
document.getElementById('lib-step-action').value = '';
|
|
5736
5738
|
document.getElementById('lib-step-tags').value = '';
|
|
5737
|
-
document.getElementById('library-step-modal-title').textContent = 'Add
|
|
5739
|
+
document.getElementById('library-step-modal-title').textContent = 'Add element to library';
|
|
5738
5740
|
document.getElementById('library-step-modal').style.display = 'flex';
|
|
5739
5741
|
}
|
|
5740
5742
|
|
|
@@ -5743,11 +5745,10 @@ function editLibraryStep(id) {
|
|
|
5743
5745
|
if (!step) return;
|
|
5744
5746
|
document.getElementById('lib-step-id').value = step.id;
|
|
5745
5747
|
document.getElementById('lib-step-name').value = step.name || '';
|
|
5746
|
-
document.getElementById('lib-step-action').value = step.action || 'click';
|
|
5747
5748
|
document.getElementById('lib-step-selector').value = step.stableSelector || step.selector || '';
|
|
5748
|
-
document.getElementById('lib-step-
|
|
5749
|
+
document.getElementById('lib-step-action').value = step.defaultAction || '';
|
|
5749
5750
|
document.getElementById('lib-step-tags').value = (step.tags || []).join(', ');
|
|
5750
|
-
document.getElementById('library-step-modal-title').textContent = 'Edit
|
|
5751
|
+
document.getElementById('library-step-modal-title').textContent = 'Edit element';
|
|
5751
5752
|
document.getElementById('library-step-modal').style.display = 'flex';
|
|
5752
5753
|
}
|
|
5753
5754
|
|
|
@@ -5758,22 +5759,22 @@ function closeLibraryStepModal() {
|
|
|
5758
5759
|
async function saveLibraryStep() {
|
|
5759
5760
|
const id = document.getElementById('lib-step-id').value;
|
|
5760
5761
|
const name = document.getElementById('lib-step-name').value.trim();
|
|
5761
|
-
const action = document.getElementById('lib-step-action').value;
|
|
5762
5762
|
const selector = document.getElementById('lib-step-selector').value.trim();
|
|
5763
|
-
const
|
|
5763
|
+
const defaultAction = document.getElementById('lib-step-action').value;
|
|
5764
5764
|
const tags = (document.getElementById('lib-step-tags').value || '').split(',').map(t=>t.trim()).filter(Boolean);
|
|
5765
5765
|
|
|
5766
|
-
if (!name) { showToast('
|
|
5766
|
+
if (!name) { showToast('Element name is required'); return; }
|
|
5767
|
+
if (!selector) { showToast('Selector is required'); return; }
|
|
5767
5768
|
|
|
5768
5769
|
try {
|
|
5769
|
-
const body = { name,
|
|
5770
|
+
const body = { name, selector, stableSelector: selector, defaultAction: defaultAction || null, tags };
|
|
5770
5771
|
const url = id ? `${API_BASE}/api/step-library/${encodeURIComponent(id)}` : `${API_BASE}/api/step-library`;
|
|
5771
5772
|
const method = id ? 'PUT' : 'POST';
|
|
5772
5773
|
const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
5773
5774
|
if (!res.ok) throw new Error(await res.text());
|
|
5774
5775
|
closeLibraryStepModal();
|
|
5775
5776
|
await loadLibraryView();
|
|
5776
|
-
showToast(id ? '
|
|
5777
|
+
showToast(id ? 'Element updated' : 'Element added to library');
|
|
5777
5778
|
} catch (err) { showToast('Error: ' + err.message); }
|
|
5778
5779
|
}
|
|
5779
5780
|
|
|
@@ -5857,23 +5858,26 @@ function renderBuilderLibrary() {
|
|
|
5857
5858
|
const actionColors = { click:'#22d3ee', type:'#a78bfa', check:'#34d399', assert:'#fb923c', select:'#f472b6', scroll:'#6b7280' };
|
|
5858
5859
|
|
|
5859
5860
|
let steps = libraryStepsCache;
|
|
5860
|
-
if (builderActionFilter) steps = steps.filter(s => s.
|
|
5861
|
+
if (builderActionFilter) steps = steps.filter(s => s.defaultAction === builderActionFilter);
|
|
5861
5862
|
if (q) steps = steps.filter(s => (s.name||'').toLowerCase().includes(q) || (s.selector||'').toLowerCase().includes(q));
|
|
5862
5863
|
|
|
5863
5864
|
if (steps.length === 0) {
|
|
5864
|
-
container.innerHTML = `<div style="padding:24px 16px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">No
|
|
5865
|
+
container.innerHTML = `<div style="padding:24px 16px;text-align:center;color:var(--muted);font-family:var(--mono);font-size:12px">No elements match</div>`;
|
|
5865
5866
|
return;
|
|
5866
5867
|
}
|
|
5867
5868
|
|
|
5868
5869
|
container.innerHTML = steps.map(s => `
|
|
5869
|
-
<div
|
|
5870
|
-
style="
|
|
5871
|
-
|
|
5872
|
-
<div style="display:flex;
|
|
5873
|
-
|
|
5874
|
-
|
|
5870
|
+
<div style="padding:10px 16px;border-bottom:1px solid var(--border)">
|
|
5871
|
+
<div style="font-size:12px;color:var(--text);margin-bottom:4px">${escapeHtml(s.name||'')}</div>
|
|
5872
|
+
<code style="font-size:10px;color:var(--muted2);display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:8px">${escapeHtml(s.stableSelector||s.selector||'')}</code>
|
|
5873
|
+
<div style="display:flex;gap:4px;flex-wrap:wrap">
|
|
5874
|
+
${['click','type','check','assert','select'].map(a => `
|
|
5875
|
+
<button onclick="addBuilderStepFromLibrary('${escapeAttr(s.id)}','${a}')"
|
|
5876
|
+
style="font-family:var(--mono);font-size:10px;padding:3px 8px;border-radius:4px;border:1px solid ${actionColors[a]};background:transparent;color:${actionColors[a]};cursor:pointer;transition:background 0.1s"
|
|
5877
|
+
onmouseover="this.style.background='${actionColors[a]}22'" onmouseout="this.style.background='transparent'">
|
|
5878
|
+
${a}
|
|
5879
|
+
</button>`).join('')}
|
|
5875
5880
|
</div>
|
|
5876
|
-
<code style="font-size:10px;color:var(--muted2);margin-left:48px;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(s.stableSelector||s.selector||'')}</code>
|
|
5877
5881
|
</div>`).join('');
|
|
5878
5882
|
}
|
|
5879
5883
|
|
|
@@ -5881,17 +5885,16 @@ function filterBuilderLibrary() {
|
|
|
5881
5885
|
renderBuilderLibrary();
|
|
5882
5886
|
}
|
|
5883
5887
|
|
|
5884
|
-
function addBuilderStepFromLibrary(id) {
|
|
5888
|
+
function addBuilderStepFromLibrary(id, action) {
|
|
5885
5889
|
const libStep = libraryStepsCache.find(s => s.id === id);
|
|
5886
5890
|
if (!libStep) return;
|
|
5887
5891
|
const step = {
|
|
5888
5892
|
id: 'bs-' + Math.random().toString(36).slice(2, 8),
|
|
5889
|
-
action: libStep.
|
|
5893
|
+
action: action || libStep.defaultAction || 'click',
|
|
5890
5894
|
stableSelector: libStep.stableSelector || libStep.selector,
|
|
5891
5895
|
selector: libStep.selector || libStep.stableSelector,
|
|
5892
|
-
value:
|
|
5893
|
-
assertType:
|
|
5894
|
-
attribute: libStep.attribute || null,
|
|
5896
|
+
value: action === 'type' ? '' : null,
|
|
5897
|
+
assertType: action === 'assert' ? 'visible' : null,
|
|
5895
5898
|
description: libStep.name || libStep.description,
|
|
5896
5899
|
libraryId: libStep.id,
|
|
5897
5900
|
timestamp: Date.now(),
|
|
@@ -6082,8 +6085,10 @@ function captureNextInteraction() {
|
|
|
6082
6085
|
|
|
6083
6086
|
async function saveHarvestedStep(step) {
|
|
6084
6087
|
const actionColors = { click:'#22d3ee', type:'#a78bfa', check:'#34d399', assert:'#fb923c', select:'#f472b6', scroll:'#6b7280' };
|
|
6085
|
-
const name = harvesterState.stepName || step.description || (step.action + ' ' + (step.stableSelector || step.selector || '').slice(0, 40));
|
|
6086
6088
|
const sel = step.stableSelector || step.selector || '';
|
|
6089
|
+
// Strip action prefix from description for cleaner element name
|
|
6090
|
+
let name = harvesterState.stepName || step.description || '';
|
|
6091
|
+
name = name.replace(/^(click|type|check|assert|select|scroll)\s+/i, '').trim() || sel.slice(0, 50);
|
|
6087
6092
|
|
|
6088
6093
|
try {
|
|
6089
6094
|
const res = await fetch(API_BASE + '/api/step-library', {
|
|
@@ -6091,10 +6096,10 @@ async function saveHarvestedStep(step) {
|
|
|
6091
6096
|
headers: { 'Content-Type': 'application/json' },
|
|
6092
6097
|
body: JSON.stringify({
|
|
6093
6098
|
name,
|
|
6094
|
-
action: step.action,
|
|
6095
6099
|
selector: sel,
|
|
6096
6100
|
stableSelector: sel,
|
|
6097
|
-
|
|
6101
|
+
defaultAction: step.action || null,
|
|
6102
|
+
defaultValue: step.value || null,
|
|
6098
6103
|
assertType: step.assertType || null,
|
|
6099
6104
|
description: step.description || null,
|
|
6100
6105
|
tags: [],
|
|
@@ -6102,19 +6107,17 @@ async function saveHarvestedStep(step) {
|
|
|
6102
6107
|
});
|
|
6103
6108
|
if (!res.ok) throw new Error(await res.text());
|
|
6104
6109
|
|
|
6105
|
-
|
|
6106
|
-
document.getElementById('harvester-status').textContent = 'Step saved to library ✓';
|
|
6110
|
+
document.getElementById('harvester-status').textContent = 'Element saved to library ✓';
|
|
6107
6111
|
document.getElementById('harvester-captured').style.display = 'flex';
|
|
6108
6112
|
document.getElementById('harvester-captured-desc').innerHTML =
|
|
6109
|
-
`<span style="font-family:var(--mono);font-size:11px;color:${actionColors[step.action]||'var(--muted)'};margin-right:8px">${step.action}</span>${escapeHtml(name)}`;
|
|
6113
|
+
`<span style="font-family:var(--mono);font-size:11px;color:${actionColors[step.action]||'var(--muted)'};margin-right:8px">${step.action||''}</span>${escapeHtml(name)}`;
|
|
6110
6114
|
document.getElementById('harvester-captured-sel').textContent = sel;
|
|
6111
6115
|
document.getElementById('btn-harvest-another').style.display = '';
|
|
6112
6116
|
document.getElementById('btn-capture-next').style.display = 'none';
|
|
6113
6117
|
|
|
6114
|
-
// Refresh library if visible
|
|
6115
6118
|
if (libraryStepsCache.length > 0) fetchLibrarySteps().then(() => { if (document.getElementById('view-step-library')?.classList.contains('active')) filterLibrarySteps(); });
|
|
6116
6119
|
} catch (err) {
|
|
6117
|
-
showToast('Failed to save
|
|
6120
|
+
showToast('Failed to save element: ' + err.message);
|
|
6118
6121
|
document.getElementById('btn-capture-next').disabled = false;
|
|
6119
6122
|
document.getElementById('btn-capture-next').textContent = 'Capture next interaction';
|
|
6120
6123
|
}
|