skopix 2.0.37 → 2.0.38
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/cli/commands/dashboard.js +126 -15
- package/package.json +1 -1
- package/web/app/index.html +154 -10
|
@@ -1823,6 +1823,32 @@ export async function dashboardCommand(options) {
|
|
|
1823
1823
|
sendJSON(res, 200, await listLibrarySteps(suitesDir));
|
|
1824
1824
|
return;
|
|
1825
1825
|
}
|
|
1826
|
+
if (pathname === '/api/step-library/pending' && method === 'GET') {
|
|
1827
|
+
sendJSON(res, 200, await listPendingSteps());
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
if (pathname.match(/^\/api\/step-library\/pending\/[^/]+\/approve$/) && method === 'POST') {
|
|
1831
|
+
const id = decodeURIComponent(pathname.split('/')[4]);
|
|
1832
|
+
const step = await approvePendingStep(suitesDir, id);
|
|
1833
|
+
sendJSON(res, 200, step || { error: 'Not found' });
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
if (pathname.match(/^\/api\/step-library\/pending\/[^/]+\/dismiss$/) && method === 'POST') {
|
|
1837
|
+
const id = decodeURIComponent(pathname.split('/')[4]);
|
|
1838
|
+
await dismissPendingStep(id);
|
|
1839
|
+
sendJSON(res, 200, { dismissed: true });
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (pathname === '/api/step-library/pending/approve-all' && method === 'POST') {
|
|
1843
|
+
const count = await approveAllPending(suitesDir);
|
|
1844
|
+
sendJSON(res, 200, { approved: count });
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
if (pathname === '/api/step-library/pending/dismiss-all' && method === 'POST') {
|
|
1848
|
+
await dismissAllPending();
|
|
1849
|
+
sendJSON(res, 200, { dismissed: true });
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1826
1852
|
if (pathname === '/api/step-library' && method === 'POST') {
|
|
1827
1853
|
const data = JSON.parse(await readBody(req));
|
|
1828
1854
|
const step = await addLibraryStep(suitesDir, data);
|
|
@@ -3652,6 +3678,66 @@ async function syncIssuesStatus() {
|
|
|
3652
3678
|
// ═══════════════════════════════════════════════════════════════
|
|
3653
3679
|
|
|
3654
3680
|
const LIBRARY_FILE = () => path.join(os.homedir(), '.skopix', 'step-library.yaml');
|
|
3681
|
+
const LIBRARY_PENDING_FILE = () => path.join(os.homedir(), '.skopix', 'step-library-pending.yaml');
|
|
3682
|
+
|
|
3683
|
+
async function listPendingSteps() {
|
|
3684
|
+
const file = LIBRARY_PENDING_FILE();
|
|
3685
|
+
if (!await fs.pathExists(file)) return [];
|
|
3686
|
+
try {
|
|
3687
|
+
const content = await fs.readFile(file, 'utf8');
|
|
3688
|
+
const data = yaml.parse(content);
|
|
3689
|
+
return Array.isArray(data) ? data : [];
|
|
3690
|
+
} catch { return []; }
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
async function savePendingSteps(steps) {
|
|
3694
|
+
const file = LIBRARY_PENDING_FILE();
|
|
3695
|
+
await fs.ensureDir(path.dirname(file));
|
|
3696
|
+
await fs.writeFile(file, yaml.stringify(steps));
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
async function addToPending(suitesDir, pendingStep) {
|
|
3700
|
+
const pending = await listPendingSteps();
|
|
3701
|
+
const existing = await listLibrarySteps(suitesDir);
|
|
3702
|
+
// Check not already in library or pending
|
|
3703
|
+
const allSels = [...existing, ...pending].map(e => (e.stableSelector||e.selector||'').toLowerCase());
|
|
3704
|
+
const selLower = (pendingStep.stableSelector||pendingStep.selector||'').toLowerCase();
|
|
3705
|
+
if (allSels.includes(selLower)) return; // already exists
|
|
3706
|
+
// Check similarity
|
|
3707
|
+
const similar = [...existing, ...pending].find(e => stepSimilarity(e, pendingStep) >= 0.85);
|
|
3708
|
+
if (similar) return;
|
|
3709
|
+
pending.push({ ...pendingStep, id: 'pending-' + Math.random().toString(36).slice(2, 10), addedAt: new Date().toISOString() });
|
|
3710
|
+
await savePendingSteps(pending);
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
async function approvePendingStep(suitesDir, id) {
|
|
3714
|
+
const pending = await listPendingSteps();
|
|
3715
|
+
const step = pending.find(s => s.id === id);
|
|
3716
|
+
if (!step) return null;
|
|
3717
|
+
const { id: _id, addedAt: _a, ...stepData } = step;
|
|
3718
|
+
const added = await addLibraryStep(suitesDir, stepData);
|
|
3719
|
+
await savePendingSteps(pending.filter(s => s.id !== id));
|
|
3720
|
+
return added;
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
async function dismissPendingStep(id) {
|
|
3724
|
+
const pending = await listPendingSteps();
|
|
3725
|
+
await savePendingSteps(pending.filter(s => s.id !== id));
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
async function approveAllPending(suitesDir) {
|
|
3729
|
+
const pending = await listPendingSteps();
|
|
3730
|
+
for (const step of pending) {
|
|
3731
|
+
const { id: _id, addedAt: _a, ...stepData } = step;
|
|
3732
|
+
await addLibraryStep(suitesDir, stepData);
|
|
3733
|
+
}
|
|
3734
|
+
await savePendingSteps([]);
|
|
3735
|
+
return pending.length;
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
async function dismissAllPending() {
|
|
3739
|
+
await savePendingSteps([]);
|
|
3740
|
+
}
|
|
3655
3741
|
|
|
3656
3742
|
async function listLibrarySteps(suitesDir) {
|
|
3657
3743
|
const file = LIBRARY_FILE();
|
|
@@ -3733,19 +3819,41 @@ async function extractStepsToLibrary(suitesDir, steps, testName) {
|
|
|
3733
3819
|
const sel = step.stableSelector || step.selector;
|
|
3734
3820
|
if (!sel) continue;
|
|
3735
3821
|
const selLower = sel.toLowerCase();
|
|
3736
|
-
|
|
3822
|
+
|
|
3823
|
+
// Check exact match first
|
|
3737
3824
|
if (seenSelectors.has(selLower)) {
|
|
3738
|
-
const
|
|
3739
|
-
|
|
3740
|
-
if (
|
|
3825
|
+
const match = existing.find(e => (e.stableSelector||e.selector||'').toLowerCase() === selLower) ||
|
|
3826
|
+
toAdd.find(e => (e.stableSelector||e.selector||'').toLowerCase() === selLower);
|
|
3827
|
+
if (match) match.usageCount = (match.usageCount || 0) + 1;
|
|
3741
3828
|
continue;
|
|
3742
3829
|
}
|
|
3830
|
+
|
|
3831
|
+
// Check similarity against existing library entries (catches slight variations)
|
|
3832
|
+
const similarExisting = existing.find(e => stepSimilarity(e, step) >= 0.85);
|
|
3833
|
+
if (similarExisting) {
|
|
3834
|
+
similarExisting.usageCount = (similarExisting.usageCount || 0) + 1;
|
|
3835
|
+
// If new selector is more stable (has pi-test-identifier), upgrade it
|
|
3836
|
+
if (sel.includes('pi-test-identifier') && !(similarExisting.stableSelector||'').includes('pi-test-identifier')) {
|
|
3837
|
+
similarExisting.stableSelector = sel;
|
|
3838
|
+
similarExisting.selector = sel;
|
|
3839
|
+
}
|
|
3840
|
+
seenSelectors.add(selLower);
|
|
3841
|
+
continue;
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
// Check similarity against steps we're about to add
|
|
3845
|
+
const similarNew = toAdd.find(e => stepSimilarity(e, step) >= 0.85);
|
|
3846
|
+
if (similarNew) {
|
|
3847
|
+
similarNew.usageCount = (similarNew.usageCount || 0) + 1;
|
|
3848
|
+
seenSelectors.add(selLower);
|
|
3849
|
+
continue;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3743
3852
|
seenSelectors.add(selLower);
|
|
3744
3853
|
// Generate clean name — strip action prefix
|
|
3745
3854
|
let name = step.description || '';
|
|
3746
3855
|
name = name.replace(/^(click|type|check|assert|select)\s+/i, '').trim() || sel.slice(0, 50);
|
|
3747
3856
|
toAdd.push({
|
|
3748
|
-
id: 'lib-' + Math.random().toString(36).slice(2, 10),
|
|
3749
3857
|
name,
|
|
3750
3858
|
selector: sel,
|
|
3751
3859
|
stableSelector: step.stableSelector || sel,
|
|
@@ -3757,29 +3865,32 @@ async function extractStepsToLibrary(suitesDir, steps, testName) {
|
|
|
3757
3865
|
tags: [],
|
|
3758
3866
|
usageCount: 1,
|
|
3759
3867
|
sourceTest: testName,
|
|
3760
|
-
createdAt: new Date().toISOString(),
|
|
3761
3868
|
});
|
|
3762
3869
|
}
|
|
3763
3870
|
|
|
3764
|
-
|
|
3765
|
-
|
|
3871
|
+
// Add new elements to pending review queue (not directly to library)
|
|
3872
|
+
for (const step of toAdd) {
|
|
3873
|
+
await addToPending(suitesDir, step);
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// Save updated usage counts for existing entries
|
|
3877
|
+
if (existing.some(e => e.usageCount !== undefined)) {
|
|
3878
|
+
await saveLibrarySteps(existing);
|
|
3766
3879
|
}
|
|
3767
3880
|
}
|
|
3768
3881
|
|
|
3769
3882
|
// Import steps from all existing tests into library
|
|
3770
3883
|
async function importStepsFromTests(suitesDir) {
|
|
3771
3884
|
const allTests = await listAllTests(suitesDir);
|
|
3772
|
-
let
|
|
3773
|
-
let skipped = 0;
|
|
3885
|
+
let queued = 0;
|
|
3774
3886
|
for (const test of allTests) {
|
|
3775
3887
|
if (!test.steps || test.steps.length === 0) continue;
|
|
3776
|
-
const before = (await
|
|
3888
|
+
const before = (await listPendingSteps()).length;
|
|
3777
3889
|
await extractStepsToLibrary(suitesDir, test.steps, test.name);
|
|
3778
|
-
const after = (await
|
|
3779
|
-
|
|
3780
|
-
skipped += test.steps.length - (after - before);
|
|
3890
|
+
const after = (await listPendingSteps()).length;
|
|
3891
|
+
queued += after - before;
|
|
3781
3892
|
}
|
|
3782
|
-
return {
|
|
3893
|
+
return { queued, message: `${queued} elements added to review queue` };
|
|
3783
3894
|
}
|
|
3784
3895
|
|
|
3785
3896
|
// Sync existing tests — replace matching steps with library steps
|
package/package.json
CHANGED
package/web/app/index.html
CHANGED
|
@@ -1321,6 +1321,7 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
1321
1321
|
<a class="nav-item" data-view="step-library">
|
|
1322
1322
|
<span class="nav-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="4" rx="1"/><rect x="3" y="10" width="18" height="4" rx="1"/><rect x="3" y="17" width="18" height="4" rx="1"/></svg></span>
|
|
1323
1323
|
Step library
|
|
1324
|
+
<span id="lib-pending-badge" style="display:none;background:#f59e0b;color:#000;font-size:9px;font-weight:700;padding:1px 5px;border-radius:8px;margin-left:4px;font-family:var(--mono)"></span>
|
|
1324
1325
|
</a>
|
|
1325
1326
|
|
|
1326
1327
|
|
|
@@ -1589,6 +1590,23 @@ body.viewer-mode .saved-test-row { cursor: default !important; }
|
|
|
1589
1590
|
</div>
|
|
1590
1591
|
</div>
|
|
1591
1592
|
|
|
1593
|
+
<!-- Pending review section -->
|
|
1594
|
+
<div id="library-pending-section" style="display:none;margin-bottom:16px">
|
|
1595
|
+
<div class="card" style="border-color:rgba(245,158,11,0.3);background:rgba(245,158,11,0.04)">
|
|
1596
|
+
<div class="card-header" style="border-color:rgba(245,158,11,0.2)">
|
|
1597
|
+
<div class="card-title" style="color:#f59e0b">
|
|
1598
|
+
⏳ Pending review
|
|
1599
|
+
<span id="pending-count-label" style="font-family:var(--mono);font-size:11px;color:var(--muted);font-weight:400;margin-left:8px"></span>
|
|
1600
|
+
</div>
|
|
1601
|
+
<div style="display:flex;gap:8px">
|
|
1602
|
+
<button class="btn btn-ghost" style="font-size:11px;padding:4px 10px" onclick="dismissAllPendingUI()">Dismiss all</button>
|
|
1603
|
+
<button class="btn btn-primary" style="font-size:11px;padding:4px 10px;background:#f59e0b;border-color:#f59e0b;color:#000" onclick="approveAllPendingUI()">Add all to library</button>
|
|
1604
|
+
</div>
|
|
1605
|
+
</div>
|
|
1606
|
+
<div id="pending-steps-list" style="padding:0"></div>
|
|
1607
|
+
</div>
|
|
1608
|
+
</div>
|
|
1609
|
+
|
|
1592
1610
|
<!-- Search + filter -->
|
|
1593
1611
|
<div style="display:flex;gap:10px;margin-bottom:16px">
|
|
1594
1612
|
<input class="form-input" id="library-search" type="text" placeholder="Search elements..." oninput="filterLibrarySteps()" style="flex:1">
|
|
@@ -2791,6 +2809,7 @@ async function refreshAll() {
|
|
|
2791
2809
|
renderConfig();
|
|
2792
2810
|
renderSuites();
|
|
2793
2811
|
populateSuiteSelect();
|
|
2812
|
+
checkPendingCount();
|
|
2794
2813
|
}
|
|
2795
2814
|
|
|
2796
2815
|
function renderStats() {
|
|
@@ -5549,8 +5568,122 @@ async function saveOllamaConfig() {
|
|
|
5549
5568
|
}
|
|
5550
5569
|
}
|
|
5551
5570
|
|
|
5552
|
-
// ── STEP LIBRARY
|
|
5553
|
-
let
|
|
5571
|
+
// ── STEP LIBRARY PENDING REVIEW ──────────────────────────────────────────────
|
|
5572
|
+
let pendingStepsCache = [];
|
|
5573
|
+
|
|
5574
|
+
async function fetchPendingSteps() {
|
|
5575
|
+
try {
|
|
5576
|
+
const res = await fetch(API_BASE + '/api/step-library/pending');
|
|
5577
|
+
if (!res.ok) return [];
|
|
5578
|
+
pendingStepsCache = await res.json();
|
|
5579
|
+
return pendingStepsCache;
|
|
5580
|
+
} catch { return []; }
|
|
5581
|
+
}
|
|
5582
|
+
|
|
5583
|
+
function updatePendingBadge(count) {
|
|
5584
|
+
const badge = document.getElementById('lib-pending-badge');
|
|
5585
|
+
if (!badge) return;
|
|
5586
|
+
if (count > 0) { badge.textContent = count; badge.style.display = ''; }
|
|
5587
|
+
else { badge.style.display = 'none'; }
|
|
5588
|
+
}
|
|
5589
|
+
|
|
5590
|
+
function renderPendingSteps(steps) {
|
|
5591
|
+
const section = document.getElementById('library-pending-section');
|
|
5592
|
+
const list = document.getElementById('pending-steps-list');
|
|
5593
|
+
const countLabel = document.getElementById('pending-count-label');
|
|
5594
|
+
if (!section || !list) return;
|
|
5595
|
+
|
|
5596
|
+
if (!steps || steps.length === 0) {
|
|
5597
|
+
section.style.display = 'none';
|
|
5598
|
+
updatePendingBadge(0);
|
|
5599
|
+
return;
|
|
5600
|
+
}
|
|
5601
|
+
|
|
5602
|
+
section.style.display = '';
|
|
5603
|
+
updatePendingBadge(steps.length);
|
|
5604
|
+
if (countLabel) countLabel.textContent = `${steps.length} element${steps.length > 1 ? 's' : ''} waiting`;
|
|
5605
|
+
|
|
5606
|
+
list.innerHTML = steps.map(s => {
|
|
5607
|
+
const sel = s.stableSelector || s.selector || '';
|
|
5608
|
+
const fragile = isSelectorFragile(sel);
|
|
5609
|
+
return `
|
|
5610
|
+
<div style="display:flex;align-items:center;gap:12px;padding:12px 20px;border-bottom:1px solid rgba(245,158,11,0.1)" data-pending-id="${escapeAttr(s.id)}">
|
|
5611
|
+
<div style="flex:1;min-width:0">
|
|
5612
|
+
<div style="font-size:13px;color:var(--text);margin-bottom:3px">${escapeHtml(s.name||'')}</div>
|
|
5613
|
+
<div style="display:flex;align-items:center;gap:6px">
|
|
5614
|
+
${fragile ? '<span style="color:#f59e0b;font-size:12px" title="Fragile selector">⚠</span>' : ''}
|
|
5615
|
+
<code style="font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;max-width:400px">${escapeHtml(sel)}</code>
|
|
5616
|
+
</div>
|
|
5617
|
+
${s.sourceTest ? `<div style="font-family:var(--mono);font-size:10px;color:var(--muted2);margin-top:2px">from: ${escapeHtml(s.sourceTest)}</div>` : ''}
|
|
5618
|
+
</div>
|
|
5619
|
+
<div style="display:flex;gap:6px;flex-shrink:0">
|
|
5620
|
+
<button class="btn btn-ghost pending-edit-btn" style="padding:4px 10px;font-size:11px" data-id="${escapeAttr(s.id)}">Edit</button>
|
|
5621
|
+
<button class="btn btn-ghost pending-dismiss-btn" style="padding:4px 10px;font-size:11px;color:var(--red)" data-id="${escapeAttr(s.id)}">Dismiss</button>
|
|
5622
|
+
<button class="btn btn-primary pending-approve-btn" style="padding:4px 10px;font-size:11px;background:#f59e0b;border-color:#f59e0b;color:#000" data-id="${escapeAttr(s.id)}">Add</button>
|
|
5623
|
+
</div>
|
|
5624
|
+
</div>`;
|
|
5625
|
+
}).join('');
|
|
5626
|
+
}
|
|
5627
|
+
|
|
5628
|
+
async function loadLibraryView() {
|
|
5629
|
+
const [steps, pending] = await Promise.all([fetchLibrarySteps(), fetchPendingSteps()]);
|
|
5630
|
+
populateLibraryTagFilter();
|
|
5631
|
+
renderPendingSteps(pending);
|
|
5632
|
+
renderLibrarySteps(steps);
|
|
5633
|
+
}
|
|
5634
|
+
|
|
5635
|
+
async function approvePendingUI(id) {
|
|
5636
|
+
await fetch(API_BASE + '/api/step-library/pending/' + encodeURIComponent(id) + '/approve', { method: 'POST' });
|
|
5637
|
+
await loadLibraryView();
|
|
5638
|
+
}
|
|
5639
|
+
|
|
5640
|
+
async function dismissPendingUI(id) {
|
|
5641
|
+
await fetch(API_BASE + '/api/step-library/pending/' + encodeURIComponent(id) + '/dismiss', { method: 'POST' });
|
|
5642
|
+
const pending = await fetchPendingSteps();
|
|
5643
|
+
renderPendingSteps(pending);
|
|
5644
|
+
}
|
|
5645
|
+
|
|
5646
|
+
async function approveAllPendingUI() {
|
|
5647
|
+
const res = await fetch(API_BASE + '/api/step-library/pending/approve-all', { method: 'POST' });
|
|
5648
|
+
const data = await res.json();
|
|
5649
|
+
showToast(`Added ${data.approved} elements to library`);
|
|
5650
|
+
await loadLibraryView();
|
|
5651
|
+
}
|
|
5652
|
+
|
|
5653
|
+
async function dismissAllPendingUI() {
|
|
5654
|
+
showConfirm('Dismiss all?', 'Dismiss all pending elements without adding them to the library?', async () => {
|
|
5655
|
+
await fetch(API_BASE + '/api/step-library/pending/dismiss-all', { method: 'POST' });
|
|
5656
|
+
await loadLibraryView();
|
|
5657
|
+
});
|
|
5658
|
+
}
|
|
5659
|
+
|
|
5660
|
+
// Also check pending count on page load and periodically
|
|
5661
|
+
async function checkPendingCount() {
|
|
5662
|
+
const pending = await fetchPendingSteps();
|
|
5663
|
+
updatePendingBadge(pending.length);
|
|
5664
|
+
}
|
|
5665
|
+
|
|
5666
|
+
// Event delegation for pending buttons
|
|
5667
|
+
document.addEventListener('click', (e) => {
|
|
5668
|
+
const approveBtn = e.target.closest('.pending-approve-btn');
|
|
5669
|
+
const dismissBtn = e.target.closest('.pending-dismiss-btn');
|
|
5670
|
+
const editBtn = e.target.closest('.pending-edit-btn');
|
|
5671
|
+
if (approveBtn) approvePendingUI(approveBtn.dataset.id);
|
|
5672
|
+
if (dismissBtn) dismissPendingUI(dismissBtn.dataset.id);
|
|
5673
|
+
if (editBtn) {
|
|
5674
|
+
// Open edit modal pre-filled with pending step data
|
|
5675
|
+
const step = pendingStepsCache.find(s => s.id === editBtn.dataset.id);
|
|
5676
|
+
if (step) {
|
|
5677
|
+
document.getElementById('lib-step-id').value = 'pending:' + step.id;
|
|
5678
|
+
document.getElementById('lib-step-name').value = step.name || '';
|
|
5679
|
+
document.getElementById('lib-step-selector').value = step.stableSelector || step.selector || '';
|
|
5680
|
+
document.getElementById('lib-step-action').value = step.defaultAction || '';
|
|
5681
|
+
document.getElementById('lib-step-tags').value = (step.tags || []).join(', ');
|
|
5682
|
+
document.getElementById('library-step-modal-title').textContent = 'Edit before adding';
|
|
5683
|
+
document.getElementById('library-step-modal').style.display = 'flex';
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
});
|
|
5554
5687
|
|
|
5555
5688
|
async function fetchLibrarySteps() {
|
|
5556
5689
|
try {
|
|
@@ -5861,7 +5994,9 @@ function closeLibraryStepModal() {
|
|
|
5861
5994
|
}
|
|
5862
5995
|
|
|
5863
5996
|
async function saveLibraryStep() {
|
|
5864
|
-
const
|
|
5997
|
+
const rawId = document.getElementById('lib-step-id').value;
|
|
5998
|
+
const isPending = rawId.startsWith('pending:');
|
|
5999
|
+
const id = isPending ? rawId.slice(8) : rawId;
|
|
5865
6000
|
const name = document.getElementById('lib-step-name').value.trim();
|
|
5866
6001
|
const selector = document.getElementById('lib-step-selector').value.trim();
|
|
5867
6002
|
const defaultAction = document.getElementById('lib-step-action').value;
|
|
@@ -5871,14 +6006,23 @@ async function saveLibraryStep() {
|
|
|
5871
6006
|
if (!selector) { showToast('Selector is required'); return; }
|
|
5872
6007
|
|
|
5873
6008
|
try {
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
6009
|
+
if (isPending) {
|
|
6010
|
+
// Dismiss pending and add directly to library with edits
|
|
6011
|
+
await fetch(API_BASE + '/api/step-library/pending/' + encodeURIComponent(id) + '/dismiss', { method: 'POST' });
|
|
6012
|
+
const body = { name, selector, stableSelector: selector, defaultAction: defaultAction || null, tags };
|
|
6013
|
+
await fetch(API_BASE + '/api/step-library', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
6014
|
+
} else if (id) {
|
|
6015
|
+
const body = { name, selector, stableSelector: selector, defaultAction: defaultAction || null, tags };
|
|
6016
|
+
const res = await fetch(API_BASE + '/api/step-library/' + encodeURIComponent(id), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
6017
|
+
if (!res.ok) throw new Error(await res.text());
|
|
6018
|
+
} else {
|
|
6019
|
+
const body = { name, selector, stableSelector: selector, defaultAction: defaultAction || null, tags };
|
|
6020
|
+
const res = await fetch(API_BASE + '/api/step-library', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
6021
|
+
if (!res.ok) throw new Error(await res.text());
|
|
6022
|
+
}
|
|
5879
6023
|
closeLibraryStepModal();
|
|
5880
6024
|
await loadLibraryView();
|
|
5881
|
-
showToast(id ? 'Element updated' : 'Element added to library');
|
|
6025
|
+
showToast(isPending ? 'Element added to library' : id ? 'Element updated' : 'Element added to library');
|
|
5882
6026
|
} catch (err) { showToast('Error: ' + err.message); }
|
|
5883
6027
|
}
|
|
5884
6028
|
|
|
@@ -5896,7 +6040,7 @@ async function importStepsFromTests() {
|
|
|
5896
6040
|
try {
|
|
5897
6041
|
const res = await fetch(API_BASE + '/api/step-library/import', { method: 'POST' });
|
|
5898
6042
|
const data = await res.json();
|
|
5899
|
-
showToast(
|
|
6043
|
+
showToast(data.queued > 0 ? `${data.queued} elements added to review queue` : 'No new elements found');
|
|
5900
6044
|
await loadLibraryView();
|
|
5901
6045
|
} catch (err) { showToast('Error: ' + err.message); }
|
|
5902
6046
|
finally { if (btn) { btn.disabled = false; btn.textContent = 'Import from all tests'; } }
|