lib-e2e-cypress-for-dummys-ts 0.1.0
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/README.md +459 -0
- package/dist/chunk-DRNRKHXN.js +60 -0
- package/dist/chunk-DRNRKHXN.js.map +1 -0
- package/dist/index.cjs +4848 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +431 -0
- package/dist/index.d.ts +431 -0
- package/dist/index.js +4355 -0
- package/dist/index.js.map +1 -0
- package/dist/selector-picker-VJOLGZ5H.js +340 -0
- package/dist/selector-picker-VJOLGZ5H.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildPickerSelector,
|
|
3
|
+
escHtml,
|
|
4
|
+
getSelectorQuality
|
|
5
|
+
} from "./chunk-DRNRKHXN.js";
|
|
6
|
+
|
|
7
|
+
// src/components/selector-picker/selector-picker.styles.ts
|
|
8
|
+
var SELECTOR_PICKER_STYLES = `
|
|
9
|
+
:host {
|
|
10
|
+
position: fixed;
|
|
11
|
+
z-index: 2147483647;
|
|
12
|
+
top: 0;
|
|
13
|
+
left: 0;
|
|
14
|
+
width: 100vw;
|
|
15
|
+
height: 100vh;
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.overlay {
|
|
20
|
+
position: absolute;
|
|
21
|
+
top: 50%;
|
|
22
|
+
left: 50%;
|
|
23
|
+
transform: translate(-50%, -50%);
|
|
24
|
+
pointer-events: all;
|
|
25
|
+
background: #161b22;
|
|
26
|
+
border: 1px solid #30363d;
|
|
27
|
+
border-radius: 10px;
|
|
28
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.6);
|
|
29
|
+
min-width: 420px;
|
|
30
|
+
max-width: 600px;
|
|
31
|
+
font-family: 'Segoe UI', system-ui, sans-serif;
|
|
32
|
+
color: #e6edf3;
|
|
33
|
+
font-size: 13px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.header {
|
|
37
|
+
padding: 12px 16px 8px;
|
|
38
|
+
border-bottom: 1px solid #21262d;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
font-size: 13px;
|
|
41
|
+
color: #8b949e;
|
|
42
|
+
letter-spacing: 0.04em;
|
|
43
|
+
text-transform: uppercase;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.list {
|
|
47
|
+
max-height: 320px;
|
|
48
|
+
overflow-y: auto;
|
|
49
|
+
padding: 6px 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.row {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 8px;
|
|
56
|
+
padding: 7px 14px;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
margin: 1px 6px;
|
|
60
|
+
transition: background 0.1s;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.row:hover,
|
|
64
|
+
.row.selected {
|
|
65
|
+
background: #21262d;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.row.selected {
|
|
69
|
+
outline: 2px solid #388bfd;
|
|
70
|
+
outline-offset: -2px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.quality-badge {
|
|
74
|
+
width: 18px;
|
|
75
|
+
height: 18px;
|
|
76
|
+
border-radius: 50%;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
font-size: 10px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.badge-excellent { background: #238636; }
|
|
85
|
+
.badge-good { background: #1f6feb; }
|
|
86
|
+
.badge-acceptable{ background: #9e6a03; }
|
|
87
|
+
.badge-poor { background: #da3633; }
|
|
88
|
+
|
|
89
|
+
.tag-name {
|
|
90
|
+
font-family: monospace;
|
|
91
|
+
font-size: 12px;
|
|
92
|
+
color: #8b949e;
|
|
93
|
+
flex-shrink: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.attr-value {
|
|
97
|
+
font-family: monospace;
|
|
98
|
+
font-size: 12px;
|
|
99
|
+
color: #c9d1d9;
|
|
100
|
+
flex: 1;
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
text-overflow: ellipsis;
|
|
103
|
+
white-space: nowrap;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.selector-preview {
|
|
107
|
+
font-family: monospace;
|
|
108
|
+
font-size: 11px;
|
|
109
|
+
color: #58a6ff;
|
|
110
|
+
flex-shrink: 0;
|
|
111
|
+
max-width: 180px;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
text-overflow: ellipsis;
|
|
114
|
+
white-space: nowrap;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.poor-warning {
|
|
118
|
+
font-size: 10px;
|
|
119
|
+
color: #f85149;
|
|
120
|
+
margin-left: auto;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.footer {
|
|
124
|
+
padding: 8px 14px 10px;
|
|
125
|
+
border-top: 1px solid #21262d;
|
|
126
|
+
display: flex;
|
|
127
|
+
gap: 16px;
|
|
128
|
+
font-size: 11px;
|
|
129
|
+
color: #484f58;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.hint kbd {
|
|
133
|
+
background: #21262d;
|
|
134
|
+
border: 1px solid #30363d;
|
|
135
|
+
border-radius: 3px;
|
|
136
|
+
padding: 1px 5px;
|
|
137
|
+
font-size: 10px;
|
|
138
|
+
color: #8b949e;
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
// src/components/selector-picker/selector-picker.template.ts
|
|
143
|
+
var QUALITY_EMOJI = {
|
|
144
|
+
excellent: "\u{1F7E2}",
|
|
145
|
+
good: "\u{1F535}",
|
|
146
|
+
acceptable: "\u{1F7E1}",
|
|
147
|
+
poor: "\u{1F534}"
|
|
148
|
+
};
|
|
149
|
+
var QUALITY_CLASS = {
|
|
150
|
+
excellent: "badge-excellent",
|
|
151
|
+
good: "badge-good",
|
|
152
|
+
acceptable: "badge-acceptable",
|
|
153
|
+
poor: "badge-poor"
|
|
154
|
+
};
|
|
155
|
+
function keyAttrDisplay(el) {
|
|
156
|
+
for (const attr of ["data-cy", "data-testid", "aria-label"]) {
|
|
157
|
+
const v = el.getAttribute(attr);
|
|
158
|
+
if (v) return `${attr}="${escHtml(v)}"`;
|
|
159
|
+
}
|
|
160
|
+
if (el.id) return `id="${escHtml(el.id)}"`;
|
|
161
|
+
if (el.className && el.className.trim()) return escHtml(el.className.trim().split(/\s+/).map((c) => `.${c}`).join(""));
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
function renderPickerRows(ancestors, selectedIndex, t) {
|
|
165
|
+
return ancestors.map((el, i) => {
|
|
166
|
+
const quality = getSelectorQuality(el);
|
|
167
|
+
const selector = buildPickerSelector(el);
|
|
168
|
+
const tag = el.tagName.toLowerCase();
|
|
169
|
+
const attr = keyAttrDisplay(el);
|
|
170
|
+
const isSelected = i === selectedIndex;
|
|
171
|
+
const isPoor = quality === "poor";
|
|
172
|
+
return `
|
|
173
|
+
<div class="row${isSelected ? " selected" : ""}" data-idx="${i}" role="option" aria-selected="${isSelected}">
|
|
174
|
+
<span class="quality-badge ${QUALITY_CLASS[quality]}" title="${escHtml(t(`SELECTOR_PICKER.QUALITY_${quality.toUpperCase()}`))}">
|
|
175
|
+
${QUALITY_EMOJI[quality]}
|
|
176
|
+
</span>
|
|
177
|
+
<span class="tag-name"><${escHtml(tag)}></span>
|
|
178
|
+
${attr ? `<span class="attr-value">${attr}</span>` : '<span class="attr-value"></span>'}
|
|
179
|
+
<span class="selector-preview">${escHtml(selector)}</span>
|
|
180
|
+
${isPoor ? `<span class="poor-warning" title="${escHtml(t("SELECTOR_PICKER.QUALITY_POOR"))}">\u26A0</span>` : ""}
|
|
181
|
+
</div>`;
|
|
182
|
+
}).join("");
|
|
183
|
+
}
|
|
184
|
+
function renderPicker(ancestors, selectedIndex, t) {
|
|
185
|
+
return `
|
|
186
|
+
<div class="overlay" role="listbox">
|
|
187
|
+
<div class="header">${escHtml(t("SELECTOR_PICKER.TITLE"))}</div>
|
|
188
|
+
<div class="list">
|
|
189
|
+
${renderPickerRows(ancestors, selectedIndex, t)}
|
|
190
|
+
</div>
|
|
191
|
+
<div class="footer">
|
|
192
|
+
<span class="hint"><kbd>\u2191\u2193</kbd> ${escHtml(t("SELECTOR_PICKER.PREVIEW_LABEL"))}</span>
|
|
193
|
+
<span class="hint"><kbd>Enter</kbd> ${escHtml(t("SELECTOR_PICKER.CONFIRM_HINT"))}</span>
|
|
194
|
+
<span class="hint"><kbd>Esc</kbd> ${escHtml(t("SELECTOR_PICKER.CANCEL_HINT"))}</span>
|
|
195
|
+
</div>
|
|
196
|
+
</div>`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/components/selector-picker/selector-picker.ts
|
|
200
|
+
var OWN_DATA_CY = "lib-e2e-cypress-for-dummys";
|
|
201
|
+
var MAX_ANCESTORS = 10;
|
|
202
|
+
var SelectorPickerElement = class extends HTMLElement {
|
|
203
|
+
targetElement = null;
|
|
204
|
+
recording;
|
|
205
|
+
translation;
|
|
206
|
+
shadow;
|
|
207
|
+
ancestors = [];
|
|
208
|
+
selectedIndex = 0;
|
|
209
|
+
keyHandler = null;
|
|
210
|
+
unsubRecording = null;
|
|
211
|
+
unsubPause = null;
|
|
212
|
+
constructor() {
|
|
213
|
+
super();
|
|
214
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
215
|
+
}
|
|
216
|
+
connectedCallback() {
|
|
217
|
+
this.setAttribute("data-cy", "lib-e2e-cypress-for-dummys");
|
|
218
|
+
this.ancestors = this.buildAncestors();
|
|
219
|
+
this.selectedIndex = this.findBestIndex();
|
|
220
|
+
this.render();
|
|
221
|
+
this.attachKeyListener();
|
|
222
|
+
this.attachRowClickListeners();
|
|
223
|
+
this.subscribeToRecordingChanges();
|
|
224
|
+
}
|
|
225
|
+
disconnectedCallback() {
|
|
226
|
+
this.detachKeyListener();
|
|
227
|
+
this.unsubRecording?.();
|
|
228
|
+
this.unsubPause?.();
|
|
229
|
+
}
|
|
230
|
+
t(key) {
|
|
231
|
+
return this.translation?.translate(key) ?? key;
|
|
232
|
+
}
|
|
233
|
+
// ── Ancestor chain ──────────────────────────────────────────────────────────
|
|
234
|
+
buildAncestors() {
|
|
235
|
+
const chain = [];
|
|
236
|
+
let current = this.targetElement;
|
|
237
|
+
while (current && current.tagName.toLowerCase() !== "html" && chain.length < MAX_ANCESTORS) {
|
|
238
|
+
if (current.getAttribute("data-cy") === OWN_DATA_CY) {
|
|
239
|
+
current = current.parentElement;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
chain.push(current);
|
|
243
|
+
current = current.parentElement;
|
|
244
|
+
}
|
|
245
|
+
return chain;
|
|
246
|
+
}
|
|
247
|
+
findBestIndex() {
|
|
248
|
+
for (let i = 0; i < this.ancestors.length; i++) {
|
|
249
|
+
const q = getSelectorQuality(this.ancestors[i]);
|
|
250
|
+
if (q === "excellent" || q === "good" || q === "acceptable") return i;
|
|
251
|
+
}
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
// ── Rendering ───────────────────────────────────────────────────────────────
|
|
255
|
+
render() {
|
|
256
|
+
this.shadow.innerHTML = `<style>${SELECTOR_PICKER_STYLES}</style>${renderPicker(
|
|
257
|
+
this.ancestors,
|
|
258
|
+
this.selectedIndex,
|
|
259
|
+
this.t.bind(this)
|
|
260
|
+
)}`;
|
|
261
|
+
}
|
|
262
|
+
// ── Keyboard ────────────────────────────────────────────────────────────────
|
|
263
|
+
attachKeyListener() {
|
|
264
|
+
this.keyHandler = (e) => {
|
|
265
|
+
if (e.key === "ArrowDown") {
|
|
266
|
+
e.stopPropagation();
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
this.selectedIndex = (this.selectedIndex + 1) % this.ancestors.length;
|
|
269
|
+
this.render();
|
|
270
|
+
this.attachRowClickListeners();
|
|
271
|
+
} else if (e.key === "ArrowUp") {
|
|
272
|
+
e.stopPropagation();
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
this.selectedIndex = (this.selectedIndex - 1 + this.ancestors.length) % this.ancestors.length;
|
|
275
|
+
this.render();
|
|
276
|
+
this.attachRowClickListeners();
|
|
277
|
+
} else if (e.key === "Enter") {
|
|
278
|
+
e.stopPropagation();
|
|
279
|
+
this.confirm(this.selectedIndex);
|
|
280
|
+
} else if (e.key === "Escape") {
|
|
281
|
+
e.stopPropagation();
|
|
282
|
+
this.cancel();
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
window.addEventListener("keydown", this.keyHandler, { capture: true });
|
|
286
|
+
}
|
|
287
|
+
detachKeyListener() {
|
|
288
|
+
if (this.keyHandler) {
|
|
289
|
+
window.removeEventListener("keydown", this.keyHandler, { capture: true });
|
|
290
|
+
this.keyHandler = null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// ── Row click listeners ─────────────────────────────────────────────────────
|
|
294
|
+
attachRowClickListeners() {
|
|
295
|
+
this.shadow.querySelectorAll("[data-idx]").forEach((row) => {
|
|
296
|
+
row.addEventListener("click", () => {
|
|
297
|
+
const idx = parseInt(row.dataset["idx"] ?? "0", 10);
|
|
298
|
+
this.confirm(idx);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
// ── Recording auto-close ────────────────────────────────────────────────────
|
|
303
|
+
subscribeToRecordingChanges() {
|
|
304
|
+
this.unsubRecording = this.recording.onRecordingChange((isRecording) => {
|
|
305
|
+
if (!isRecording) this.closeSilently();
|
|
306
|
+
});
|
|
307
|
+
this.unsubPause = this.recording.onPauseChange((isPaused) => {
|
|
308
|
+
if (isPaused) this.closeSilently();
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
// ── Actions ─────────────────────────────────────────────────────────────────
|
|
312
|
+
confirm(index) {
|
|
313
|
+
const el = this.ancestors[index];
|
|
314
|
+
if (!el) return;
|
|
315
|
+
const selector = buildPickerSelector(el);
|
|
316
|
+
const command = `cy.get('${selector}').click()`;
|
|
317
|
+
this.recording.appendCommand(command);
|
|
318
|
+
this.dispatchEvent(new CustomEvent("selectorchosen", { detail: command, bubbles: true }));
|
|
319
|
+
this.closeSilently();
|
|
320
|
+
}
|
|
321
|
+
cancel() {
|
|
322
|
+
this.dispatchEvent(new CustomEvent("pickercancelled", { bubbles: true }));
|
|
323
|
+
this.closeSilently();
|
|
324
|
+
}
|
|
325
|
+
closeSilently() {
|
|
326
|
+
this.detachKeyListener();
|
|
327
|
+
this.unsubRecording?.();
|
|
328
|
+
this.unsubPause?.();
|
|
329
|
+
this.unsubRecording = null;
|
|
330
|
+
this.unsubPause = null;
|
|
331
|
+
this.remove();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
if (!customElements.get("selector-picker")) {
|
|
335
|
+
customElements.define("selector-picker", SelectorPickerElement);
|
|
336
|
+
}
|
|
337
|
+
export {
|
|
338
|
+
SelectorPickerElement
|
|
339
|
+
};
|
|
340
|
+
//# sourceMappingURL=selector-picker-VJOLGZ5H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/selector-picker/selector-picker.styles.ts","../src/components/selector-picker/selector-picker.template.ts","../src/components/selector-picker/selector-picker.ts"],"sourcesContent":["export const SELECTOR_PICKER_STYLES = `\r\n :host {\r\n position: fixed;\r\n z-index: 2147483647;\r\n top: 0;\r\n left: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n pointer-events: none;\r\n }\r\n\r\n .overlay {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n pointer-events: all;\r\n background: #161b22;\r\n border: 1px solid #30363d;\r\n border-radius: 10px;\r\n box-shadow: 0 8px 32px rgba(0,0,0,0.6);\r\n min-width: 420px;\r\n max-width: 600px;\r\n font-family: 'Segoe UI', system-ui, sans-serif;\r\n color: #e6edf3;\r\n font-size: 13px;\r\n }\r\n\r\n .header {\r\n padding: 12px 16px 8px;\r\n border-bottom: 1px solid #21262d;\r\n font-weight: 600;\r\n font-size: 13px;\r\n color: #8b949e;\r\n letter-spacing: 0.04em;\r\n text-transform: uppercase;\r\n }\r\n\r\n .list {\r\n max-height: 320px;\r\n overflow-y: auto;\r\n padding: 6px 0;\r\n }\r\n\r\n .row {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 7px 14px;\r\n cursor: pointer;\r\n border-radius: 6px;\r\n margin: 1px 6px;\r\n transition: background 0.1s;\r\n }\r\n\r\n .row:hover,\r\n .row.selected {\r\n background: #21262d;\r\n }\r\n\r\n .row.selected {\r\n outline: 2px solid #388bfd;\r\n outline-offset: -2px;\r\n }\r\n\r\n .quality-badge {\r\n width: 18px;\r\n height: 18px;\r\n border-radius: 50%;\r\n flex-shrink: 0;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 10px;\r\n }\r\n\r\n .badge-excellent { background: #238636; }\r\n .badge-good { background: #1f6feb; }\r\n .badge-acceptable{ background: #9e6a03; }\r\n .badge-poor { background: #da3633; }\r\n\r\n .tag-name {\r\n font-family: monospace;\r\n font-size: 12px;\r\n color: #8b949e;\r\n flex-shrink: 0;\r\n }\r\n\r\n .attr-value {\r\n font-family: monospace;\r\n font-size: 12px;\r\n color: #c9d1d9;\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n .selector-preview {\r\n font-family: monospace;\r\n font-size: 11px;\r\n color: #58a6ff;\r\n flex-shrink: 0;\r\n max-width: 180px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n .poor-warning {\r\n font-size: 10px;\r\n color: #f85149;\r\n margin-left: auto;\r\n }\r\n\r\n .footer {\r\n padding: 8px 14px 10px;\r\n border-top: 1px solid #21262d;\r\n display: flex;\r\n gap: 16px;\r\n font-size: 11px;\r\n color: #484f58;\r\n }\r\n\r\n .hint kbd {\r\n background: #21262d;\r\n border: 1px solid #30363d;\r\n border-radius: 3px;\r\n padding: 1px 5px;\r\n font-size: 10px;\r\n color: #8b949e;\r\n }\r\n`;\r\n","import { getSelectorQuality, buildPickerSelector, type SelectorQuality } from '../../utils/selector-quality.utils';\r\nimport { escHtml } from '../../utils/html.utils';\r\n\r\nconst QUALITY_EMOJI: Record<SelectorQuality, string> = {\r\n excellent: '🟢',\r\n good: '🔵',\r\n acceptable: '🟡',\r\n poor: '🔴',\r\n};\r\n\r\nconst QUALITY_CLASS: Record<SelectorQuality, string> = {\r\n excellent: 'badge-excellent',\r\n good: 'badge-good',\r\n acceptable: 'badge-acceptable',\r\n poor: 'badge-poor',\r\n};\r\n\r\nfunction keyAttrDisplay(el: HTMLElement): string {\r\n for (const attr of ['data-cy', 'data-testid', 'aria-label']) {\r\n const v = el.getAttribute(attr);\r\n if (v) return `${attr}=\"${escHtml(v)}\"`;\r\n }\r\n if (el.id) return `id=\"${escHtml(el.id)}\"`;\r\n if (el.className && el.className.trim()) return escHtml(el.className.trim().split(/\\s+/).map((c) => `.${c}`).join(''));\r\n return '';\r\n}\r\n\r\nexport function renderPickerRows(\r\n ancestors: HTMLElement[],\r\n selectedIndex: number,\r\n t: (key: string) => string,\r\n): string {\r\n return ancestors.map((el, i) => {\r\n const quality = getSelectorQuality(el);\r\n const selector = buildPickerSelector(el);\r\n const tag = el.tagName.toLowerCase();\r\n const attr = keyAttrDisplay(el);\r\n const isSelected = i === selectedIndex;\r\n const isPoor = quality === 'poor';\r\n\r\n return `\r\n <div class=\"row${isSelected ? ' selected' : ''}\" data-idx=\"${i}\" role=\"option\" aria-selected=\"${isSelected}\">\r\n <span class=\"quality-badge ${QUALITY_CLASS[quality]}\" title=\"${escHtml(t(`SELECTOR_PICKER.QUALITY_${quality.toUpperCase()}`))}\">\r\n ${QUALITY_EMOJI[quality]}\r\n </span>\r\n <span class=\"tag-name\"><${escHtml(tag)}></span>\r\n ${attr ? `<span class=\"attr-value\">${attr}</span>` : '<span class=\"attr-value\"></span>'}\r\n <span class=\"selector-preview\">${escHtml(selector)}</span>\r\n ${isPoor ? `<span class=\"poor-warning\" title=\"${escHtml(t('SELECTOR_PICKER.QUALITY_POOR'))}\">⚠</span>` : ''}\r\n </div>`;\r\n }).join('');\r\n}\r\n\r\nexport function renderPicker(\r\n ancestors: HTMLElement[],\r\n selectedIndex: number,\r\n t: (key: string) => string,\r\n): string {\r\n return `\r\n <div class=\"overlay\" role=\"listbox\">\r\n <div class=\"header\">${escHtml(t('SELECTOR_PICKER.TITLE'))}</div>\r\n <div class=\"list\">\r\n ${renderPickerRows(ancestors, selectedIndex, t)}\r\n </div>\r\n <div class=\"footer\">\r\n <span class=\"hint\"><kbd>↑↓</kbd> ${escHtml(t('SELECTOR_PICKER.PREVIEW_LABEL'))}</span>\r\n <span class=\"hint\"><kbd>Enter</kbd> ${escHtml(t('SELECTOR_PICKER.CONFIRM_HINT'))}</span>\r\n <span class=\"hint\"><kbd>Esc</kbd> ${escHtml(t('SELECTOR_PICKER.CANCEL_HINT'))}</span>\r\n </div>\r\n </div>`;\r\n}\r\n","import type { RecordingService } from '../../services/recording.service';\r\nimport type { TranslationService } from '../../services/translation.service';\r\nimport { getSelectorQuality, buildPickerSelector } from '../../utils/selector-quality.utils';\r\nimport { SELECTOR_PICKER_STYLES } from './selector-picker.styles';\r\nimport { renderPicker } from './selector-picker.template';\r\n\r\nconst OWN_DATA_CY = 'lib-e2e-cypress-for-dummys';\r\nconst MAX_ANCESTORS = 10;\r\n\r\nexport class SelectorPickerElement extends HTMLElement {\r\n targetElement: HTMLElement | null = null;\r\n recording!: RecordingService;\r\n translation!: TranslationService;\r\n\r\n private shadow: ShadowRoot;\r\n private ancestors: HTMLElement[] = [];\r\n private selectedIndex = 0;\r\n private keyHandler: ((e: KeyboardEvent) => void) | null = null;\r\n private unsubRecording: (() => void) | null = null;\r\n private unsubPause: (() => void) | null = null;\r\n\r\n constructor() {\r\n super();\r\n this.shadow = this.attachShadow({ mode: 'open' });\r\n }\r\n\r\n connectedCallback(): void {\r\n this.setAttribute('data-cy', 'lib-e2e-cypress-for-dummys');\r\n this.ancestors = this.buildAncestors();\r\n this.selectedIndex = this.findBestIndex();\r\n this.render();\r\n this.attachKeyListener();\r\n this.attachRowClickListeners();\r\n this.subscribeToRecordingChanges();\r\n }\r\n\r\n disconnectedCallback(): void {\r\n this.detachKeyListener();\r\n this.unsubRecording?.();\r\n this.unsubPause?.();\r\n }\r\n\r\n private t(key: string): string {\r\n return this.translation?.translate(key) ?? key;\r\n }\r\n\r\n // ── Ancestor chain ──────────────────────────────────────────────────────────\r\n\r\n private buildAncestors(): HTMLElement[] {\r\n const chain: HTMLElement[] = [];\r\n let current: HTMLElement | null = this.targetElement;\r\n\r\n while (current && current.tagName.toLowerCase() !== 'html' && chain.length < MAX_ANCESTORS) {\r\n if (current.getAttribute('data-cy') === OWN_DATA_CY) {\r\n current = current.parentElement;\r\n continue;\r\n }\r\n chain.push(current);\r\n current = current.parentElement;\r\n }\r\n\r\n return chain;\r\n }\r\n\r\n private findBestIndex(): number {\r\n for (let i = 0; i < this.ancestors.length; i++) {\r\n const q = getSelectorQuality(this.ancestors[i]);\r\n if (q === 'excellent' || q === 'good' || q === 'acceptable') return i;\r\n }\r\n return 0;\r\n }\r\n\r\n // ── Rendering ───────────────────────────────────────────────────────────────\r\n\r\n private render(): void {\r\n this.shadow.innerHTML = `<style>${SELECTOR_PICKER_STYLES}</style>${renderPicker(\r\n this.ancestors,\r\n this.selectedIndex,\r\n this.t.bind(this),\r\n )}`;\r\n }\r\n\r\n // ── Keyboard ────────────────────────────────────────────────────────────────\r\n\r\n private attachKeyListener(): void {\r\n this.keyHandler = (e: KeyboardEvent) => {\r\n if (e.key === 'ArrowDown') {\r\n e.stopPropagation();\r\n e.preventDefault();\r\n this.selectedIndex = (this.selectedIndex + 1) % this.ancestors.length;\r\n this.render();\r\n this.attachRowClickListeners();\r\n } else if (e.key === 'ArrowUp') {\r\n e.stopPropagation();\r\n e.preventDefault();\r\n this.selectedIndex = (this.selectedIndex - 1 + this.ancestors.length) % this.ancestors.length;\r\n this.render();\r\n this.attachRowClickListeners();\r\n } else if (e.key === 'Enter') {\r\n e.stopPropagation();\r\n this.confirm(this.selectedIndex);\r\n } else if (e.key === 'Escape') {\r\n e.stopPropagation();\r\n this.cancel();\r\n }\r\n };\r\n window.addEventListener('keydown', this.keyHandler, { capture: true });\r\n }\r\n\r\n private detachKeyListener(): void {\r\n if (this.keyHandler) {\r\n window.removeEventListener('keydown', this.keyHandler, { capture: true });\r\n this.keyHandler = null;\r\n }\r\n }\r\n\r\n // ── Row click listeners ─────────────────────────────────────────────────────\r\n\r\n private attachRowClickListeners(): void {\r\n this.shadow.querySelectorAll('[data-idx]').forEach((row) => {\r\n (row as HTMLElement).addEventListener('click', () => {\r\n const idx = parseInt((row as HTMLElement).dataset['idx'] ?? '0', 10);\r\n this.confirm(idx);\r\n });\r\n });\r\n }\r\n\r\n // ── Recording auto-close ────────────────────────────────────────────────────\r\n\r\n private subscribeToRecordingChanges(): void {\r\n this.unsubRecording = this.recording.onRecordingChange((isRecording) => {\r\n if (!isRecording) this.closeSilently();\r\n });\r\n this.unsubPause = this.recording.onPauseChange((isPaused) => {\r\n if (isPaused) this.closeSilently();\r\n });\r\n }\r\n\r\n // ── Actions ─────────────────────────────────────────────────────────────────\r\n\r\n private confirm(index: number): void {\r\n const el = this.ancestors[index];\r\n if (!el) return;\r\n const selector = buildPickerSelector(el);\r\n const command = `cy.get('${selector}').click()`;\r\n this.recording.appendCommand(command);\r\n this.dispatchEvent(new CustomEvent('selectorchosen', { detail: command, bubbles: true }));\r\n this.closeSilently();\r\n }\r\n\r\n private cancel(): void {\r\n this.dispatchEvent(new CustomEvent('pickercancelled', { bubbles: true }));\r\n this.closeSilently();\r\n }\r\n\r\n private closeSilently(): void {\r\n this.detachKeyListener();\r\n this.unsubRecording?.();\r\n this.unsubPause?.();\r\n this.unsubRecording = null;\r\n this.unsubPause = null;\r\n this.remove();\r\n }\r\n}\r\n\r\nif (!customElements.get('selector-picker')) {\r\n customElements.define('selector-picker', SelectorPickerElement);\r\n}\r\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lib-e2e-cypress-for-dummys-ts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cypress E2E test recorder — pure TypeScript, zero Angular dependencies",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"author": "Gonzalo Carmenado <gonzalo.carmenado@gmail.com>",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/GonzaloCarmenado/lib-e2e-cypress-for-dummys-ts.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/GonzaloCarmenado/lib-e2e-cypress-for-dummys-ts#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/GonzaloCarmenado/lib-e2e-cypress-for-dummys-ts/issues"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"cypress",
|
|
31
|
+
"e2e",
|
|
32
|
+
"testing",
|
|
33
|
+
"recorder",
|
|
34
|
+
"test-generator",
|
|
35
|
+
"typescript",
|
|
36
|
+
"custom-element",
|
|
37
|
+
"web-component"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"build:watch": "tsup --watch",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest",
|
|
44
|
+
"test:coverage": "vitest run --coverage",
|
|
45
|
+
"lint": "eslint",
|
|
46
|
+
"lint:fix": "eslint --fix",
|
|
47
|
+
"prepare": "husky",
|
|
48
|
+
"prepublishOnly": "npm run lint && npm test && npm run build"
|
|
49
|
+
},
|
|
50
|
+
"lint-staged": {
|
|
51
|
+
"*.ts": "eslint --fix"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@eslint/js": "^10.0.1",
|
|
55
|
+
"@types/node": "^22.0.0",
|
|
56
|
+
"@vitest/coverage-v8": "^2.1.0",
|
|
57
|
+
"eslint": "^10.3.0",
|
|
58
|
+
"fake-indexeddb": "^6.2.5",
|
|
59
|
+
"husky": "^9.1.7",
|
|
60
|
+
"jsdom": "^25.0.0",
|
|
61
|
+
"lint-staged": "^17.0.2",
|
|
62
|
+
"tsup": "^8.3.0",
|
|
63
|
+
"typescript": "~5.7.2",
|
|
64
|
+
"typescript-eslint": "^8.59.2",
|
|
65
|
+
"vitest": "^2.1.0"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"idb": "^8.0.3",
|
|
69
|
+
"sweetalert2": "^11.26.24"
|
|
70
|
+
}
|
|
71
|
+
}
|