lib-e2e-cypress-for-dummys-ts 0.2.0 → 0.5.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 +634 -466
- package/dist/{selector-picker-VJOLGZ5H.js → chunk-FDMUIHDZ.js} +95 -38
- package/dist/chunk-FDMUIHDZ.js.map +1 -0
- package/dist/index.cjs +1238 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -47
- package/dist/index.d.ts +235 -47
- package/dist/index.js +1174 -143
- package/dist/index.js.map +1 -1
- package/dist/runner.js +107 -0
- package/dist/selector-picker-NKYFFFGK.js +7 -0
- package/dist/selector-picker-NKYFFFGK.js.map +1 -0
- package/package.json +4 -1
- package/dist/chunk-DRNRKHXN.js +0 -60
- package/dist/chunk-DRNRKHXN.js.map +0 -1
- package/dist/selector-picker-VJOLGZ5H.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -61,6 +61,25 @@ function buildPickerSelector(el) {
|
|
|
61
61
|
}
|
|
62
62
|
return el.tagName.toLowerCase();
|
|
63
63
|
}
|
|
64
|
+
function keyAttrDisplay(el) {
|
|
65
|
+
for (const attr of ["data-cy", "data-testid", "aria-label"]) {
|
|
66
|
+
const v = el.getAttribute(attr);
|
|
67
|
+
if (v) return `${attr}="${v}"`;
|
|
68
|
+
}
|
|
69
|
+
if (el.id) return `id="${el.id}"`;
|
|
70
|
+
if (el.className && el.className.trim()) {
|
|
71
|
+
return el.className.trim().split(/\s+/).map((c) => `.${c}`).join("");
|
|
72
|
+
}
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
function describePickerRow(el) {
|
|
76
|
+
return {
|
|
77
|
+
quality: getSelectorQuality(el),
|
|
78
|
+
selector: buildPickerSelector(el),
|
|
79
|
+
tag: el.tagName.toLowerCase(),
|
|
80
|
+
attr: keyAttrDisplay(el)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
64
83
|
var FORBIDDEN_ID_PREFIXES;
|
|
65
84
|
var init_selector_quality_utils = __esm({
|
|
66
85
|
"src/utils/selector-quality.utils.ts"() {
|
|
@@ -80,19 +99,6 @@ var init_selector_quality_utils = __esm({
|
|
|
80
99
|
}
|
|
81
100
|
});
|
|
82
101
|
|
|
83
|
-
// src/utils/html.utils.ts
|
|
84
|
-
function escHtml(s) {
|
|
85
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
86
|
-
}
|
|
87
|
-
function escAttr(s) {
|
|
88
|
-
return s.replace(/"/g, """).replace(/</g, "<");
|
|
89
|
-
}
|
|
90
|
-
var init_html_utils = __esm({
|
|
91
|
-
"src/utils/html.utils.ts"() {
|
|
92
|
-
"use strict";
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
102
|
// src/components/selector-picker/selector-picker.styles.ts
|
|
97
103
|
var SELECTOR_PICKER_STYLES;
|
|
98
104
|
var init_selector_picker_styles = __esm({
|
|
@@ -234,42 +240,42 @@ var init_selector_picker_styles = __esm({
|
|
|
234
240
|
}
|
|
235
241
|
});
|
|
236
242
|
|
|
237
|
-
// src/
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (el.id) return `id="${escHtml(el.id)}"`;
|
|
244
|
-
if (el.className && el.className.trim()) return escHtml(el.className.trim().split(/\s+/).map((c) => `.${c}`).join(""));
|
|
245
|
-
return "";
|
|
243
|
+
// src/utils/html.utils.ts
|
|
244
|
+
function escHtml(s) {
|
|
245
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
246
|
+
}
|
|
247
|
+
function escAttr(s) {
|
|
248
|
+
return s.replace(/"/g, """).replace(/</g, "<");
|
|
246
249
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
var init_html_utils = __esm({
|
|
251
|
+
"src/utils/html.utils.ts"() {
|
|
252
|
+
"use strict";
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// src/components/selector-picker/selector-picker.template.ts
|
|
257
|
+
function renderPickerRows(rows, selectedIndex, t) {
|
|
258
|
+
return rows.map((row, i) => {
|
|
253
259
|
const isSelected = i === selectedIndex;
|
|
254
|
-
const isPoor = quality === "poor";
|
|
260
|
+
const isPoor = row.quality === "poor";
|
|
255
261
|
return `
|
|
256
262
|
<div class="row${isSelected ? " selected" : ""}" data-idx="${i}" role="option" aria-selected="${isSelected}">
|
|
257
|
-
<span class="quality-badge ${QUALITY_CLASS[quality]}" title="${escHtml(t(`SELECTOR_PICKER.QUALITY_${quality.toUpperCase()}`))}">
|
|
258
|
-
${QUALITY_EMOJI[quality]}
|
|
263
|
+
<span class="quality-badge ${QUALITY_CLASS[row.quality]}" title="${escHtml(t(`SELECTOR_PICKER.QUALITY_${row.quality.toUpperCase()}`))}">
|
|
264
|
+
${QUALITY_EMOJI[row.quality]}
|
|
259
265
|
</span>
|
|
260
|
-
<span class="tag-name"><${escHtml(tag)}></span>
|
|
261
|
-
${attr ? `<span class="attr-value">${attr}</span>` : '<span class="attr-value"></span>'}
|
|
262
|
-
<span class="selector-preview">${escHtml(selector)}</span>
|
|
266
|
+
<span class="tag-name"><${escHtml(row.tag)}></span>
|
|
267
|
+
${row.attr ? `<span class="attr-value">${escHtml(row.attr)}</span>` : '<span class="attr-value"></span>'}
|
|
268
|
+
<span class="selector-preview">${escHtml(row.selector)}</span>
|
|
263
269
|
${isPoor ? `<span class="poor-warning" title="${escHtml(t("SELECTOR_PICKER.QUALITY_POOR"))}">\u26A0</span>` : ""}
|
|
264
270
|
</div>`;
|
|
265
271
|
}).join("");
|
|
266
272
|
}
|
|
267
|
-
function renderPicker(
|
|
273
|
+
function renderPicker(rows, selectedIndex, t) {
|
|
268
274
|
return `
|
|
269
275
|
<div class="overlay" role="listbox">
|
|
270
276
|
<div class="header">${escHtml(t("SELECTOR_PICKER.TITLE"))}</div>
|
|
271
277
|
<div class="list">
|
|
272
|
-
${renderPickerRows(
|
|
278
|
+
${renderPickerRows(rows, selectedIndex, t)}
|
|
273
279
|
</div>
|
|
274
280
|
<div class="footer">
|
|
275
281
|
<span class="hint"><kbd>\u2191\u2193</kbd> ${escHtml(t("SELECTOR_PICKER.PREVIEW_LABEL"))}</span>
|
|
@@ -282,7 +288,6 @@ var QUALITY_EMOJI, QUALITY_CLASS;
|
|
|
282
288
|
var init_selector_picker_template = __esm({
|
|
283
289
|
"src/components/selector-picker/selector-picker.template.ts"() {
|
|
284
290
|
"use strict";
|
|
285
|
-
init_selector_quality_utils();
|
|
286
291
|
init_html_utils();
|
|
287
292
|
QUALITY_EMOJI = {
|
|
288
293
|
excellent: "\u{1F7E2}",
|
|
@@ -319,6 +324,7 @@ var init_selector_picker = __esm({
|
|
|
319
324
|
translation;
|
|
320
325
|
shadow;
|
|
321
326
|
ancestors = [];
|
|
327
|
+
rows = [];
|
|
322
328
|
selectedIndex = 0;
|
|
323
329
|
keyHandler = null;
|
|
324
330
|
unsubRecording = null;
|
|
@@ -330,6 +336,7 @@ var init_selector_picker = __esm({
|
|
|
330
336
|
connectedCallback() {
|
|
331
337
|
this.setAttribute("data-cy", "lib-e2e-cypress-for-dummys");
|
|
332
338
|
this.ancestors = this.buildAncestors();
|
|
339
|
+
this.rows = this.ancestors.map(describePickerRow);
|
|
333
340
|
this.selectedIndex = this.findBestIndex();
|
|
334
341
|
this.render();
|
|
335
342
|
this.attachKeyListener();
|
|
@@ -359,8 +366,8 @@ var init_selector_picker = __esm({
|
|
|
359
366
|
return chain;
|
|
360
367
|
}
|
|
361
368
|
findBestIndex() {
|
|
362
|
-
for (let i = 0; i < this.
|
|
363
|
-
const q =
|
|
369
|
+
for (let i = 0; i < this.rows.length; i++) {
|
|
370
|
+
const q = this.rows[i].quality;
|
|
364
371
|
if (q === "excellent" || q === "good" || q === "acceptable") return i;
|
|
365
372
|
}
|
|
366
373
|
return 0;
|
|
@@ -368,7 +375,7 @@ var init_selector_picker = __esm({
|
|
|
368
375
|
// ── Rendering ───────────────────────────────────────────────────────────────
|
|
369
376
|
render() {
|
|
370
377
|
this.shadow.innerHTML = `<style>${SELECTOR_PICKER_STYLES}</style>${renderPicker(
|
|
371
|
-
this.
|
|
378
|
+
this.rows,
|
|
372
379
|
this.selectedIndex,
|
|
373
380
|
this.t.bind(this)
|
|
374
381
|
)}`;
|
|
@@ -424,16 +431,15 @@ var init_selector_picker = __esm({
|
|
|
424
431
|
}
|
|
425
432
|
// ── Actions ─────────────────────────────────────────────────────────────────
|
|
426
433
|
confirm(index) {
|
|
427
|
-
const
|
|
428
|
-
if (!
|
|
429
|
-
const
|
|
430
|
-
const command = `cy.get('${selector}').click()`;
|
|
434
|
+
const row = this.rows[index];
|
|
435
|
+
if (!row) return;
|
|
436
|
+
const command = `cy.get('${row.selector}').click()`;
|
|
431
437
|
this.recording.appendCommand(command);
|
|
432
|
-
this.dispatchEvent(new CustomEvent("selectorchosen", { detail: command, bubbles: true }));
|
|
438
|
+
this.dispatchEvent(new CustomEvent("selectorchosen", { detail: command, bubbles: true, composed: true }));
|
|
433
439
|
this.closeSilently();
|
|
434
440
|
}
|
|
435
441
|
cancel() {
|
|
436
|
-
this.dispatchEvent(new CustomEvent("pickercancelled", { bubbles: true }));
|
|
442
|
+
this.dispatchEvent(new CustomEvent("pickercancelled", { bubbles: true, composed: true }));
|
|
437
443
|
this.closeSilently();
|
|
438
444
|
}
|
|
439
445
|
closeSilently() {
|
|
@@ -454,21 +460,26 @@ var init_selector_picker = __esm({
|
|
|
454
460
|
// src/index.ts
|
|
455
461
|
var index_exports = {};
|
|
456
462
|
__export(index_exports, {
|
|
463
|
+
ACTIVE_SESSION_BREADCRUMB_KEY: () => ACTIVE_SESSION_BREADCRUMB_KEY,
|
|
457
464
|
AdvancedTestEditorElement: () => AdvancedTestEditorElement,
|
|
458
465
|
AdvancedTestTransformationService: () => AdvancedTestTransformationService,
|
|
459
466
|
ConfigurationElement: () => ConfigurationElement,
|
|
460
467
|
DB_SCHEMA: () => DB_SCHEMA,
|
|
461
468
|
DB_STORE_NAMES: () => DB_STORE_NAMES,
|
|
469
|
+
DEFAULT_RESUME_TTL_MINUTES: () => DEFAULT_RESUME_TTL_MINUTES,
|
|
462
470
|
FilePreviewElement: () => FilePreviewElement,
|
|
463
471
|
HttpMonitor: () => HttpMonitor,
|
|
464
472
|
INPUT_TYPES: () => INPUT_TYPES,
|
|
465
473
|
LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES: () => LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
|
|
474
|
+
LOCALE_BY_LANG: () => LOCALE_BY_LANG,
|
|
466
475
|
LibE2eRecorderElement: () => LibE2eRecorderElement,
|
|
467
476
|
PersistenceService: () => PersistenceService,
|
|
477
|
+
RESUME_TTL_CONFIG_KEY: () => RESUME_TTL_CONFIG_KEY,
|
|
468
478
|
RecordingService: () => RecordingService,
|
|
469
479
|
SCROLLBAR_STYLES: () => SCROLLBAR_STYLES,
|
|
470
480
|
SUPPORTED_LANGS: () => SUPPORTED_LANGS,
|
|
471
481
|
SaveTestElement: () => SaveTestElement,
|
|
482
|
+
SelectorPickerElement: () => SelectorPickerElement,
|
|
472
483
|
Subject: () => Subject,
|
|
473
484
|
TestEditorElement: () => TestEditorElement,
|
|
474
485
|
TestPrevisualizerElement: () => TestPrevisualizerElement,
|
|
@@ -479,10 +490,13 @@ __export(index_exports, {
|
|
|
479
490
|
generateAlias: () => generateAlias,
|
|
480
491
|
injectStyles: () => injectStyles,
|
|
481
492
|
isLang: () => isLang,
|
|
493
|
+
isLocalHost: () => isLocalHost,
|
|
494
|
+
localeForLang: () => localeForLang,
|
|
482
495
|
makeModalResizable: () => makeModalResizable,
|
|
483
496
|
makeSwalDraggable: () => makeSwalDraggable,
|
|
484
497
|
makeSwalDraggableByContentId: () => makeSwalDraggableByContentId,
|
|
485
498
|
persistenceService: () => persistenceService,
|
|
499
|
+
selectTestsForExport: () => selectTestsForExport,
|
|
486
500
|
setSwal2DataCyAttribute: () => setSwal2DataCyAttribute,
|
|
487
501
|
showToast: () => showToast,
|
|
488
502
|
transformationService: () => transformationService,
|
|
@@ -495,6 +509,16 @@ var SUPPORTED_LANGS = ["es", "en", "fr", "it", "de"];
|
|
|
495
509
|
function isLang(value) {
|
|
496
510
|
return SUPPORTED_LANGS.includes(value);
|
|
497
511
|
}
|
|
512
|
+
var LOCALE_BY_LANG = {
|
|
513
|
+
es: "es-ES",
|
|
514
|
+
en: "en-GB",
|
|
515
|
+
fr: "fr-FR",
|
|
516
|
+
it: "it-IT",
|
|
517
|
+
de: "de-DE"
|
|
518
|
+
};
|
|
519
|
+
function localeForLang(lang) {
|
|
520
|
+
return isLang(lang) ? LOCALE_BY_LANG[lang] : "es-ES";
|
|
521
|
+
}
|
|
498
522
|
|
|
499
523
|
// src/models/input-types.model.ts
|
|
500
524
|
var INPUT_TYPES = [
|
|
@@ -511,7 +535,7 @@ var INPUT_TYPES = [
|
|
|
511
535
|
// src/models/db-schema.model.ts
|
|
512
536
|
var DB_SCHEMA = {
|
|
513
537
|
name: "E2ECypressDB",
|
|
514
|
-
version:
|
|
538
|
+
version: 11,
|
|
515
539
|
stores: [
|
|
516
540
|
{
|
|
517
541
|
name: "tests",
|
|
@@ -551,11 +575,45 @@ var DB_SCHEMA = {
|
|
|
551
575
|
{ name: "extendedHttpCommands", keyPath: "extendedHttpCommands", unique: false },
|
|
552
576
|
{ name: "allowReadWriteFiles", keyPath: "allowReadWriteFiles", unique: false }
|
|
553
577
|
]
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
// Single-record store (fixed key id=1) holding the live, in-progress
|
|
581
|
+
// recording session so it survives a micro-frontend crossing or reload.
|
|
582
|
+
// See docs/specs/006-cross-app-recording-continuity.md.
|
|
583
|
+
name: "activeSession",
|
|
584
|
+
keyPath: "id",
|
|
585
|
+
autoIncrement: false,
|
|
586
|
+
indexes: []
|
|
554
587
|
}
|
|
555
588
|
]
|
|
556
589
|
};
|
|
557
590
|
var DB_STORE_NAMES = DB_SCHEMA.stores.map((s) => s.name);
|
|
558
591
|
|
|
592
|
+
// src/models/active-session.model.ts
|
|
593
|
+
var ACTIVE_SESSION_BREADCRUMB_KEY = "e2e-active-session";
|
|
594
|
+
var RESUME_TTL_CONFIG_KEY = "resumeRecencyTtlMinutes";
|
|
595
|
+
var DEFAULT_RESUME_TTL_MINUTES = 30;
|
|
596
|
+
|
|
597
|
+
// src/utils/subject.ts
|
|
598
|
+
var Subject = class {
|
|
599
|
+
_value;
|
|
600
|
+
listeners = /* @__PURE__ */ new Set();
|
|
601
|
+
constructor(initialValue) {
|
|
602
|
+
this._value = initialValue;
|
|
603
|
+
}
|
|
604
|
+
next(value) {
|
|
605
|
+
this._value = value;
|
|
606
|
+
this.listeners.forEach((l) => l(value));
|
|
607
|
+
}
|
|
608
|
+
getValue() {
|
|
609
|
+
return this._value;
|
|
610
|
+
}
|
|
611
|
+
subscribe(fn) {
|
|
612
|
+
this.listeners.add(fn);
|
|
613
|
+
return () => this.listeners.delete(fn);
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
|
|
559
617
|
// src/i18n/es.ts
|
|
560
618
|
var I18N_ES = {
|
|
561
619
|
MAIN_FRAME: {
|
|
@@ -578,7 +636,9 @@ var I18N_ES = {
|
|
|
578
636
|
NO_TAGS: "Sin etiquetas",
|
|
579
637
|
SAVE_BTN: "\u{1F4BE} Guardar",
|
|
580
638
|
SAVE_AND_EDIT: "\u{1F4DD} Guardar y editar",
|
|
581
|
-
REMOVE_TAG_TITLE: "Quitar"
|
|
639
|
+
REMOVE_TAG_TITLE: "Quitar",
|
|
640
|
+
NOTES_LABEL: "Descripci\xF3n / Notas (opcional):",
|
|
641
|
+
NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc."
|
|
582
642
|
},
|
|
583
643
|
TEST_EDITOR: {
|
|
584
644
|
NO_TAGS: "Sin etiquetas",
|
|
@@ -588,8 +648,6 @@ var I18N_ES = {
|
|
|
588
648
|
COPY_DESCRIBE: "\u{1F5C2} Copiar describe()",
|
|
589
649
|
MULTI_SELECT: "\u{1F5C2} Multi-select",
|
|
590
650
|
CANCEL_SELECT: "\u2715 Cancelar",
|
|
591
|
-
SECTION_COMMANDS: "Comandos",
|
|
592
|
-
SECTION_INTERCEPTORS: "Interceptores",
|
|
593
651
|
COPY_CMDS_BTN: "\u{1F4CB} Copiar comandos",
|
|
594
652
|
COPY_ICPS_BTN: "\u{1F4CB} Copiar interceptores",
|
|
595
653
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
@@ -618,6 +676,18 @@ var I18N_ES = {
|
|
|
618
676
|
DATA_SECTION: "\u{1F4BE} Datos",
|
|
619
677
|
DATA_DESC: "Exporta todos tus tests a JSON o importa una copia de seguridad.",
|
|
620
678
|
EXPORT_BTN: "\u2B06\uFE0F Exportar tests",
|
|
679
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Exportar tests",
|
|
680
|
+
EXPORT_MODE_ALL: "Todo",
|
|
681
|
+
EXPORT_MODE_MANUAL: "Selecci\xF3n manual",
|
|
682
|
+
EXPORT_MODE_TAGS: "Por tags",
|
|
683
|
+
EXPORT_ALL_DESC: "Se exportar\xE1n todos los tests guardados.",
|
|
684
|
+
EXPORT_COUNT: "A exportar:",
|
|
685
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exportar",
|
|
686
|
+
EXPORT_CANCEL: "Cancelar",
|
|
687
|
+
EXPORT_EMPTY: "No hay tests para exportar.",
|
|
688
|
+
EXPORT_NO_TAGS: "No hay tags disponibles.",
|
|
689
|
+
EXPORT_TAGS_HINT: "Selecciona uno o m\xE1s tags para ver qu\xE9 pruebas se exportar\xE1n.",
|
|
690
|
+
EXPORT_RESULT_LABEL: "Pruebas que se exportar\xE1n:",
|
|
621
691
|
IMPORT_BTN: "\u2B07\uFE0F Importar tests",
|
|
622
692
|
FOLDER_UPDATED_TOAST: "\u2713 Carpeta de Cypress actualizada",
|
|
623
693
|
FOLDER_ERROR_TOAST: "Error al acceder a la carpeta",
|
|
@@ -627,7 +697,13 @@ var I18N_ES = {
|
|
|
627
697
|
JSON_BAD_FORMAT: "El archivo no tiene el formato esperado.",
|
|
628
698
|
SMART_SELECTOR_SECTION: "\u{1F50D} Selector inteligente",
|
|
629
699
|
SMART_SELECTOR_TITLE: "Selector inteligente",
|
|
630
|
-
SMART_SELECTOR_SUB: "Muestra un picker al hacer click en elementos sin selector v\xE1lido."
|
|
700
|
+
SMART_SELECTOR_SUB: "Muestra un picker al hacer click en elementos sin selector v\xE1lido.",
|
|
701
|
+
START_HIDDEN_SECTION: "\u{1F441} Visibilidad del widget",
|
|
702
|
+
START_HIDDEN_TITLE: "Iniciar oculto",
|
|
703
|
+
START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo.",
|
|
704
|
+
RESUME_TTL_SECTION: "\u23F1 Continuidad de grabaci\xF3n",
|
|
705
|
+
RESUME_TTL_LABEL: "Reanudar sin preguntar (minutos)",
|
|
706
|
+
RESUME_TTL_HINT: "Si recargas o cambias de proyecto, una grabaci\xF3n activa se reanuda sin preguntar dentro de este margen. Pasado ese tiempo, te preguntar\xE1 si continuar o descartar."
|
|
631
707
|
},
|
|
632
708
|
SELECTOR_PICKER: {
|
|
633
709
|
TITLE: "Selecciona un elemento del DOM",
|
|
@@ -668,8 +744,10 @@ var I18N_ES = {
|
|
|
668
744
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Editar manualmente",
|
|
669
745
|
CLOSE_BTN: "\u2715 Cerrar",
|
|
670
746
|
NEW_FILE_BTN: "+ Nuevo archivo",
|
|
747
|
+
NEW_FOLDER_BTN: "+ Nueva carpeta",
|
|
671
748
|
REFRESH_BTN: "\u21BB Actualizar",
|
|
672
749
|
NEW_FILE_PLACEHOLDER: "nombre-del-test",
|
|
750
|
+
NEW_FOLDER_PLACEHOLDER: "nombre-de-carpeta",
|
|
673
751
|
NEW_FILE_CONFIRM: "Crear",
|
|
674
752
|
NEW_FILE_CANCEL: "Cancelar"
|
|
675
753
|
},
|
|
@@ -682,10 +760,19 @@ var I18N_ES = {
|
|
|
682
760
|
NO_CHANGES: "Sin cambios respecto al original",
|
|
683
761
|
NO_CHANGES_SHORT: "Sin cambios",
|
|
684
762
|
LAUNCH_BTN: "\u25B6 Lanzar test",
|
|
763
|
+
LAUNCH_LOCAL_ONLY: "Mu\xE9velo a local para poder probar",
|
|
764
|
+
LAUNCH_RUNNING: "\u23F3 Ejecutando\u2026",
|
|
765
|
+
LAUNCH_PASSED: "\u2713 Prueba superada",
|
|
766
|
+
LAUNCH_FAILED: "\u2717 Prueba fallida",
|
|
767
|
+
LAUNCH_NO_RUNNER: "No se detect\xF3 el runner local (\xBFlo has arrancado?)",
|
|
685
768
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
686
769
|
SAVE_BTN: "\u{1F4BE} Guardar",
|
|
687
770
|
CLOSE_BTN: "\u2715 Cerrar",
|
|
688
|
-
BACK_TO_EDITOR: "\u2190 Volver al editor"
|
|
771
|
+
BACK_TO_EDITOR: "\u2190 Volver al editor",
|
|
772
|
+
INSERT_BTN: "\u{1FA84} Insertar bloques",
|
|
773
|
+
INSERT_TITLE: "Insertar it() y beforeEach() en el contenido del editor",
|
|
774
|
+
INSERT_DONE: "Bloques insertados en el editor",
|
|
775
|
+
INSERT_ERROR: "No se encontr\xF3 un bloque describe() v\xE1lido en el archivo"
|
|
689
776
|
},
|
|
690
777
|
RECORDER: {
|
|
691
778
|
FS_TITLE: "\u{1F4C1} Acceso a ficheros",
|
|
@@ -714,7 +801,12 @@ var I18N_ES = {
|
|
|
714
801
|
BADGE_PAUSED: "\u23F8 PAUSA",
|
|
715
802
|
BADGE_REC: "\u25CF REC",
|
|
716
803
|
STOP_TITLE: "Detener (Ctrl+R)",
|
|
717
|
-
START_TITLE: "Grabar (Ctrl+R)"
|
|
804
|
+
START_TITLE: "Grabar (Ctrl+R)",
|
|
805
|
+
SESSION_RESUME_TITLE: "\u23FA Grabaci\xF3n en curso",
|
|
806
|
+
SESSION_RESUME_TEXT: "Hay una grabaci\xF3n activa sin terminar.",
|
|
807
|
+
SESSION_RESUME_COUNT: "comandos capturados",
|
|
808
|
+
SESSION_CONTINUE_BTN: "Continuar grabando",
|
|
809
|
+
SESSION_DISCARD_BTN: "Descartar"
|
|
718
810
|
}
|
|
719
811
|
};
|
|
720
812
|
|
|
@@ -740,7 +832,9 @@ var I18N_EN = {
|
|
|
740
832
|
NO_TAGS: "No tags",
|
|
741
833
|
SAVE_BTN: "\u{1F4BE} Save",
|
|
742
834
|
SAVE_AND_EDIT: "\u{1F4DD} Save and edit",
|
|
743
|
-
REMOVE_TAG_TITLE: "Remove"
|
|
835
|
+
REMOVE_TAG_TITLE: "Remove",
|
|
836
|
+
NOTES_LABEL: "Description / Notes (optional):",
|
|
837
|
+
NOTES_PLACEHOLDER: "What this test validates, preconditions, etc."
|
|
744
838
|
},
|
|
745
839
|
TEST_EDITOR: {
|
|
746
840
|
NO_TAGS: "No tags",
|
|
@@ -750,8 +844,6 @@ var I18N_EN = {
|
|
|
750
844
|
COPY_DESCRIBE: "\u{1F5C2} Copy describe()",
|
|
751
845
|
MULTI_SELECT: "\u{1F5C2} Multi-select",
|
|
752
846
|
CANCEL_SELECT: "\u2715 Cancel",
|
|
753
|
-
SECTION_COMMANDS: "Commands",
|
|
754
|
-
SECTION_INTERCEPTORS: "Interceptors",
|
|
755
847
|
COPY_CMDS_BTN: "\u{1F4CB} Copy commands",
|
|
756
848
|
COPY_ICPS_BTN: "\u{1F4CB} Copy interceptors",
|
|
757
849
|
DEFAULT_DESCRIBE: "Test suite",
|
|
@@ -780,6 +872,18 @@ var I18N_EN = {
|
|
|
780
872
|
DATA_SECTION: "\u{1F4BE} Data",
|
|
781
873
|
DATA_DESC: "Export all your tests to JSON or import a backup.",
|
|
782
874
|
EXPORT_BTN: "\u2B06\uFE0F Export tests",
|
|
875
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Export tests",
|
|
876
|
+
EXPORT_MODE_ALL: "All",
|
|
877
|
+
EXPORT_MODE_MANUAL: "Manual selection",
|
|
878
|
+
EXPORT_MODE_TAGS: "By tags",
|
|
879
|
+
EXPORT_ALL_DESC: "All saved tests will be exported.",
|
|
880
|
+
EXPORT_COUNT: "To export:",
|
|
881
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Export",
|
|
882
|
+
EXPORT_CANCEL: "Cancel",
|
|
883
|
+
EXPORT_EMPTY: "No tests to export.",
|
|
884
|
+
EXPORT_NO_TAGS: "No tags available.",
|
|
885
|
+
EXPORT_TAGS_HINT: "Select one or more tags to preview which tests will be exported.",
|
|
886
|
+
EXPORT_RESULT_LABEL: "Tests that will be exported:",
|
|
783
887
|
IMPORT_BTN: "\u2B07\uFE0F Import tests",
|
|
784
888
|
FOLDER_UPDATED_TOAST: "\u2713 Cypress folder updated",
|
|
785
889
|
FOLDER_ERROR_TOAST: "Error accessing the folder",
|
|
@@ -789,7 +893,13 @@ var I18N_EN = {
|
|
|
789
893
|
JSON_BAD_FORMAT: "The file does not have the expected format.",
|
|
790
894
|
SMART_SELECTOR_SECTION: "\u{1F50D} Smart selector",
|
|
791
895
|
SMART_SELECTOR_TITLE: "Smart selector",
|
|
792
|
-
SMART_SELECTOR_SUB: "Shows a picker when clicking elements with no valid selector."
|
|
896
|
+
SMART_SELECTOR_SUB: "Shows a picker when clicking elements with no valid selector.",
|
|
897
|
+
START_HIDDEN_SECTION: "\u{1F441} Widget visibility",
|
|
898
|
+
START_HIDDEN_TITLE: "Start hidden",
|
|
899
|
+
START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it.",
|
|
900
|
+
RESUME_TTL_SECTION: "\u23F1 Recording continuity",
|
|
901
|
+
RESUME_TTL_LABEL: "Resume without asking (minutes)",
|
|
902
|
+
RESUME_TTL_HINT: "If you reload or switch projects, an active recording resumes without asking within this window. After that, you will be asked to continue or discard."
|
|
793
903
|
},
|
|
794
904
|
SELECTOR_PICKER: {
|
|
795
905
|
TITLE: "Select a DOM element",
|
|
@@ -830,8 +940,10 @@ var I18N_EN = {
|
|
|
830
940
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Edit manually",
|
|
831
941
|
CLOSE_BTN: "\u2715 Close",
|
|
832
942
|
NEW_FILE_BTN: "+ New file",
|
|
943
|
+
NEW_FOLDER_BTN: "+ New folder",
|
|
833
944
|
REFRESH_BTN: "\u21BB Refresh",
|
|
834
945
|
NEW_FILE_PLACEHOLDER: "test-name",
|
|
946
|
+
NEW_FOLDER_PLACEHOLDER: "folder-name",
|
|
835
947
|
NEW_FILE_CONFIRM: "Create",
|
|
836
948
|
NEW_FILE_CANCEL: "Cancel"
|
|
837
949
|
},
|
|
@@ -844,10 +956,19 @@ var I18N_EN = {
|
|
|
844
956
|
NO_CHANGES: "No changes from original",
|
|
845
957
|
NO_CHANGES_SHORT: "No changes",
|
|
846
958
|
LAUNCH_BTN: "\u25B6 Run test",
|
|
959
|
+
LAUNCH_LOCAL_ONLY: "Move it to localhost to run tests",
|
|
960
|
+
LAUNCH_RUNNING: "\u23F3 Running\u2026",
|
|
961
|
+
LAUNCH_PASSED: "\u2713 Test passed",
|
|
962
|
+
LAUNCH_FAILED: "\u2717 Test failed",
|
|
963
|
+
LAUNCH_NO_RUNNER: "No local runner detected (did you start it?)",
|
|
847
964
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
848
965
|
SAVE_BTN: "\u{1F4BE} Save",
|
|
849
966
|
CLOSE_BTN: "\u2715 Close",
|
|
850
|
-
BACK_TO_EDITOR: "\u2190 Back to editor"
|
|
967
|
+
BACK_TO_EDITOR: "\u2190 Back to editor",
|
|
968
|
+
INSERT_BTN: "\u{1FA84} Insert blocks",
|
|
969
|
+
INSERT_TITLE: "Insert it() and beforeEach() into the editor content",
|
|
970
|
+
INSERT_DONE: "Blocks inserted into the editor",
|
|
971
|
+
INSERT_ERROR: "No valid describe() block found in the file"
|
|
851
972
|
},
|
|
852
973
|
RECORDER: {
|
|
853
974
|
FS_TITLE: "\u{1F4C1} File access",
|
|
@@ -876,7 +997,12 @@ var I18N_EN = {
|
|
|
876
997
|
BADGE_PAUSED: "\u23F8 PAUSED",
|
|
877
998
|
BADGE_REC: "\u25CF REC",
|
|
878
999
|
STOP_TITLE: "Stop (Ctrl+R)",
|
|
879
|
-
START_TITLE: "Record (Ctrl+R)"
|
|
1000
|
+
START_TITLE: "Record (Ctrl+R)",
|
|
1001
|
+
SESSION_RESUME_TITLE: "\u23FA Recording in progress",
|
|
1002
|
+
SESSION_RESUME_TEXT: "There is an unfinished active recording.",
|
|
1003
|
+
SESSION_RESUME_COUNT: "commands captured",
|
|
1004
|
+
SESSION_CONTINUE_BTN: "Keep recording",
|
|
1005
|
+
SESSION_DISCARD_BTN: "Discard"
|
|
880
1006
|
}
|
|
881
1007
|
};
|
|
882
1008
|
|
|
@@ -902,7 +1028,9 @@ var I18N_FR = {
|
|
|
902
1028
|
NO_TAGS: "Sans \xE9tiquettes",
|
|
903
1029
|
SAVE_BTN: "\u{1F4BE} Enregistrer",
|
|
904
1030
|
SAVE_AND_EDIT: "\u{1F4DD} Enregistrer et \xE9diter",
|
|
905
|
-
REMOVE_TAG_TITLE: "Retirer"
|
|
1031
|
+
REMOVE_TAG_TITLE: "Retirer",
|
|
1032
|
+
NOTES_LABEL: "Description / Notes (optionnel) :",
|
|
1033
|
+
NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc."
|
|
906
1034
|
},
|
|
907
1035
|
TEST_EDITOR: {
|
|
908
1036
|
NO_TAGS: "Sans \xE9tiquettes",
|
|
@@ -912,8 +1040,6 @@ var I18N_FR = {
|
|
|
912
1040
|
COPY_DESCRIBE: "\u{1F5C2} Copier describe()",
|
|
913
1041
|
MULTI_SELECT: "\u{1F5C2} Multi-s\xE9lection",
|
|
914
1042
|
CANCEL_SELECT: "\u2715 Annuler",
|
|
915
|
-
SECTION_COMMANDS: "Commandes",
|
|
916
|
-
SECTION_INTERCEPTORS: "Intercepteurs",
|
|
917
1043
|
COPY_CMDS_BTN: "\u{1F4CB} Copier les commandes",
|
|
918
1044
|
COPY_ICPS_BTN: "\u{1F4CB} Copier les intercepteurs",
|
|
919
1045
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
@@ -942,6 +1068,18 @@ var I18N_FR = {
|
|
|
942
1068
|
DATA_SECTION: "\u{1F4BE} Donn\xE9es",
|
|
943
1069
|
DATA_DESC: "Exportez tous vos tests en JSON ou importez une sauvegarde.",
|
|
944
1070
|
EXPORT_BTN: "\u2B06\uFE0F Exporter les tests",
|
|
1071
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Exporter les tests",
|
|
1072
|
+
EXPORT_MODE_ALL: "Tout",
|
|
1073
|
+
EXPORT_MODE_MANUAL: "S\xE9lection manuelle",
|
|
1074
|
+
EXPORT_MODE_TAGS: "Par tags",
|
|
1075
|
+
EXPORT_ALL_DESC: "Tous les tests enregistr\xE9s seront export\xE9s.",
|
|
1076
|
+
EXPORT_COUNT: "\xC0 exporter :",
|
|
1077
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exporter",
|
|
1078
|
+
EXPORT_CANCEL: "Annuler",
|
|
1079
|
+
EXPORT_EMPTY: "Aucun test \xE0 exporter.",
|
|
1080
|
+
EXPORT_NO_TAGS: "Aucun tag disponible.",
|
|
1081
|
+
EXPORT_TAGS_HINT: "S\xE9lectionnez un ou plusieurs tags pour voir les tests qui seront export\xE9s.",
|
|
1082
|
+
EXPORT_RESULT_LABEL: "Tests qui seront export\xE9s :",
|
|
945
1083
|
IMPORT_BTN: "\u2B07\uFE0F Importer les tests",
|
|
946
1084
|
FOLDER_UPDATED_TOAST: "\u2713 Dossier Cypress mis \xE0 jour",
|
|
947
1085
|
FOLDER_ERROR_TOAST: "Erreur lors de l'acc\xE8s au dossier",
|
|
@@ -951,7 +1089,13 @@ var I18N_FR = {
|
|
|
951
1089
|
JSON_BAD_FORMAT: "Le fichier n'a pas le format attendu.",
|
|
952
1090
|
SMART_SELECTOR_SECTION: "\u{1F50D} S\xE9lecteur intelligent",
|
|
953
1091
|
SMART_SELECTOR_TITLE: "S\xE9lecteur intelligent",
|
|
954
|
-
SMART_SELECTOR_SUB: "Affiche un s\xE9lecteur lors du clic sur des \xE9l\xE9ments sans s\xE9lecteur valide."
|
|
1092
|
+
SMART_SELECTOR_SUB: "Affiche un s\xE9lecteur lors du clic sur des \xE9l\xE9ments sans s\xE9lecteur valide.",
|
|
1093
|
+
START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE9 du widget",
|
|
1094
|
+
START_HIDDEN_TITLE: "D\xE9marrer masqu\xE9",
|
|
1095
|
+
START_HIDDEN_SUB: "Le widget d\xE9marre invisible. Utilisez Ctrl+Shift+E pour l'afficher ou le masquer.",
|
|
1096
|
+
RESUME_TTL_SECTION: "\u23F1 Continuit\xE9 d'enregistrement",
|
|
1097
|
+
RESUME_TTL_LABEL: "Reprendre sans demander (minutes)",
|
|
1098
|
+
RESUME_TTL_HINT: "Si vous rechargez ou changez de projet, un enregistrement actif reprend sans demander dans ce d\xE9lai. Au-del\xE0, il vous demandera de continuer ou d'ignorer."
|
|
955
1099
|
},
|
|
956
1100
|
SELECTOR_PICKER: {
|
|
957
1101
|
TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
|
|
@@ -992,8 +1136,10 @@ var I18N_FR = {
|
|
|
992
1136
|
EDIT_MANUAL_BTN: "\u270F\uFE0F \xC9diter manuellement",
|
|
993
1137
|
CLOSE_BTN: "\u2715 Fermer",
|
|
994
1138
|
NEW_FILE_BTN: "+ Nouveau fichier",
|
|
1139
|
+
NEW_FOLDER_BTN: "+ Nouveau dossier",
|
|
995
1140
|
REFRESH_BTN: "\u21BB Actualiser",
|
|
996
1141
|
NEW_FILE_PLACEHOLDER: "nom-du-test",
|
|
1142
|
+
NEW_FOLDER_PLACEHOLDER: "nom-du-dossier",
|
|
997
1143
|
NEW_FILE_CONFIRM: "Cr\xE9er",
|
|
998
1144
|
NEW_FILE_CANCEL: "Annuler"
|
|
999
1145
|
},
|
|
@@ -1006,10 +1152,19 @@ var I18N_FR = {
|
|
|
1006
1152
|
NO_CHANGES: "Aucun changement par rapport \xE0 l'original",
|
|
1007
1153
|
NO_CHANGES_SHORT: "Aucun changement",
|
|
1008
1154
|
LAUNCH_BTN: "\u25B6 Lancer le test",
|
|
1155
|
+
LAUNCH_LOCAL_ONLY: "Passe en local pour pouvoir tester",
|
|
1156
|
+
LAUNCH_RUNNING: "\u23F3 Ex\xE9cution\u2026",
|
|
1157
|
+
LAUNCH_PASSED: "\u2713 Test r\xE9ussi",
|
|
1158
|
+
LAUNCH_FAILED: "\u2717 Test \xE9chou\xE9",
|
|
1159
|
+
LAUNCH_NO_RUNNER: "Aucun runner local d\xE9tect\xE9 (l'avez-vous d\xE9marr\xE9 ?)",
|
|
1009
1160
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1010
1161
|
SAVE_BTN: "\u{1F4BE} Enregistrer",
|
|
1011
1162
|
CLOSE_BTN: "\u2715 Fermer",
|
|
1012
|
-
BACK_TO_EDITOR: "\u2190 Retour \xE0 l'\xE9diteur"
|
|
1163
|
+
BACK_TO_EDITOR: "\u2190 Retour \xE0 l'\xE9diteur",
|
|
1164
|
+
INSERT_BTN: "\u{1FA84} Ins\xE9rer les blocs",
|
|
1165
|
+
INSERT_TITLE: "Ins\xE9rer it() et beforeEach() dans le contenu de l'\xE9diteur",
|
|
1166
|
+
INSERT_DONE: "Blocs ins\xE9r\xE9s dans l'\xE9diteur",
|
|
1167
|
+
INSERT_ERROR: "Aucun bloc describe() valide trouv\xE9 dans le fichier"
|
|
1013
1168
|
},
|
|
1014
1169
|
RECORDER: {
|
|
1015
1170
|
FS_TITLE: "\u{1F4C1} Acc\xE8s aux fichiers",
|
|
@@ -1038,7 +1193,12 @@ var I18N_FR = {
|
|
|
1038
1193
|
BADGE_PAUSED: "\u23F8 PAUSE",
|
|
1039
1194
|
BADGE_REC: "\u25CF REC",
|
|
1040
1195
|
STOP_TITLE: "Arr\xEAter (Ctrl+R)",
|
|
1041
|
-
START_TITLE: "Enregistrer (Ctrl+R)"
|
|
1196
|
+
START_TITLE: "Enregistrer (Ctrl+R)",
|
|
1197
|
+
SESSION_RESUME_TITLE: "\u23FA Enregistrement en cours",
|
|
1198
|
+
SESSION_RESUME_TEXT: "Un enregistrement actif est inachev\xE9.",
|
|
1199
|
+
SESSION_RESUME_COUNT: "commandes captur\xE9es",
|
|
1200
|
+
SESSION_CONTINUE_BTN: "Continuer l'enregistrement",
|
|
1201
|
+
SESSION_DISCARD_BTN: "Ignorer"
|
|
1042
1202
|
}
|
|
1043
1203
|
};
|
|
1044
1204
|
|
|
@@ -1064,7 +1224,9 @@ var I18N_IT = {
|
|
|
1064
1224
|
NO_TAGS: "Nessuna etichetta",
|
|
1065
1225
|
SAVE_BTN: "\u{1F4BE} Salva",
|
|
1066
1226
|
SAVE_AND_EDIT: "\u{1F4DD} Salva e modifica",
|
|
1067
|
-
REMOVE_TAG_TITLE: "Rimuovi"
|
|
1227
|
+
REMOVE_TAG_TITLE: "Rimuovi",
|
|
1228
|
+
NOTES_LABEL: "Descrizione / Note (opzionale):",
|
|
1229
|
+
NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc."
|
|
1068
1230
|
},
|
|
1069
1231
|
TEST_EDITOR: {
|
|
1070
1232
|
NO_TAGS: "Nessuna etichetta",
|
|
@@ -1074,8 +1236,6 @@ var I18N_IT = {
|
|
|
1074
1236
|
COPY_DESCRIBE: "\u{1F5C2} Copia describe()",
|
|
1075
1237
|
MULTI_SELECT: "\u{1F5C2} Multi-selezione",
|
|
1076
1238
|
CANCEL_SELECT: "\u2715 Annulla",
|
|
1077
|
-
SECTION_COMMANDS: "Comandi",
|
|
1078
|
-
SECTION_INTERCEPTORS: "Interceptor",
|
|
1079
1239
|
COPY_CMDS_BTN: "\u{1F4CB} Copia comandi",
|
|
1080
1240
|
COPY_ICPS_BTN: "\u{1F4CB} Copia interceptor",
|
|
1081
1241
|
DEFAULT_DESCRIBE: "Suite di test",
|
|
@@ -1104,6 +1264,18 @@ var I18N_IT = {
|
|
|
1104
1264
|
DATA_SECTION: "\u{1F4BE} Dati",
|
|
1105
1265
|
DATA_DESC: "Esporta tutti i tuoi test in JSON o importa un backup.",
|
|
1106
1266
|
EXPORT_BTN: "\u2B06\uFE0F Esporta test",
|
|
1267
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Esporta test",
|
|
1268
|
+
EXPORT_MODE_ALL: "Tutto",
|
|
1269
|
+
EXPORT_MODE_MANUAL: "Selezione manuale",
|
|
1270
|
+
EXPORT_MODE_TAGS: "Per tag",
|
|
1271
|
+
EXPORT_ALL_DESC: "Verranno esportati tutti i test salvati.",
|
|
1272
|
+
EXPORT_COUNT: "Da esportare:",
|
|
1273
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Esporta",
|
|
1274
|
+
EXPORT_CANCEL: "Annulla",
|
|
1275
|
+
EXPORT_EMPTY: "Nessun test da esportare.",
|
|
1276
|
+
EXPORT_NO_TAGS: "Nessun tag disponibile.",
|
|
1277
|
+
EXPORT_TAGS_HINT: "Seleziona uno o pi\xF9 tag per vedere quali test verranno esportati.",
|
|
1278
|
+
EXPORT_RESULT_LABEL: "Test che verranno esportati:",
|
|
1107
1279
|
IMPORT_BTN: "\u2B07\uFE0F Importa test",
|
|
1108
1280
|
FOLDER_UPDATED_TOAST: "\u2713 Cartella Cypress aggiornata",
|
|
1109
1281
|
FOLDER_ERROR_TOAST: "Errore durante l'accesso alla cartella",
|
|
@@ -1113,7 +1285,13 @@ var I18N_IT = {
|
|
|
1113
1285
|
JSON_BAD_FORMAT: "Il file non ha il formato previsto.",
|
|
1114
1286
|
SMART_SELECTOR_SECTION: "\u{1F50D} Selettore intelligente",
|
|
1115
1287
|
SMART_SELECTOR_TITLE: "Selettore intelligente",
|
|
1116
|
-
SMART_SELECTOR_SUB: "Mostra un selettore quando si fa clic su elementi senza selettore valido."
|
|
1288
|
+
SMART_SELECTOR_SUB: "Mostra un selettore quando si fa clic su elementi senza selettore valido.",
|
|
1289
|
+
START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE0 del widget",
|
|
1290
|
+
START_HIDDEN_TITLE: "Avvia nascosto",
|
|
1291
|
+
START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo.",
|
|
1292
|
+
RESUME_TTL_SECTION: "\u23F1 Continuit\xE0 di registrazione",
|
|
1293
|
+
RESUME_TTL_LABEL: "Riprendi senza chiedere (minuti)",
|
|
1294
|
+
RESUME_TTL_HINT: "Se ricarichi o cambi progetto, una registrazione attiva riprende senza chiedere entro questo intervallo. Trascorso, ti chieder\xE0 se continuare o annullare."
|
|
1117
1295
|
},
|
|
1118
1296
|
SELECTOR_PICKER: {
|
|
1119
1297
|
TITLE: "Seleziona un elemento DOM",
|
|
@@ -1154,8 +1332,10 @@ var I18N_IT = {
|
|
|
1154
1332
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Modifica manualmente",
|
|
1155
1333
|
CLOSE_BTN: "\u2715 Chiudi",
|
|
1156
1334
|
NEW_FILE_BTN: "+ Nuovo file",
|
|
1335
|
+
NEW_FOLDER_BTN: "+ Nuova cartella",
|
|
1157
1336
|
REFRESH_BTN: "\u21BB Aggiorna",
|
|
1158
1337
|
NEW_FILE_PLACEHOLDER: "nome-del-test",
|
|
1338
|
+
NEW_FOLDER_PLACEHOLDER: "nome-cartella",
|
|
1159
1339
|
NEW_FILE_CONFIRM: "Crea",
|
|
1160
1340
|
NEW_FILE_CANCEL: "Annulla"
|
|
1161
1341
|
},
|
|
@@ -1168,10 +1348,19 @@ var I18N_IT = {
|
|
|
1168
1348
|
NO_CHANGES: "Nessuna modifica rispetto all'originale",
|
|
1169
1349
|
NO_CHANGES_SHORT: "Nessuna modifica",
|
|
1170
1350
|
LAUNCH_BTN: "\u25B6 Esegui test",
|
|
1351
|
+
LAUNCH_LOCAL_ONLY: "Spostati in locale per poter testare",
|
|
1352
|
+
LAUNCH_RUNNING: "\u23F3 Esecuzione\u2026",
|
|
1353
|
+
LAUNCH_PASSED: "\u2713 Test superato",
|
|
1354
|
+
LAUNCH_FAILED: "\u2717 Test fallito",
|
|
1355
|
+
LAUNCH_NO_RUNNER: "Nessun runner locale rilevato (l'hai avviato?)",
|
|
1171
1356
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1172
1357
|
SAVE_BTN: "\u{1F4BE} Salva",
|
|
1173
1358
|
CLOSE_BTN: "\u2715 Chiudi",
|
|
1174
|
-
BACK_TO_EDITOR: "\u2190 Torna all'editor"
|
|
1359
|
+
BACK_TO_EDITOR: "\u2190 Torna all'editor",
|
|
1360
|
+
INSERT_BTN: "\u{1FA84} Inserisci blocchi",
|
|
1361
|
+
INSERT_TITLE: "Inserisci it() e beforeEach() nel contenuto dell'editor",
|
|
1362
|
+
INSERT_DONE: "Blocchi inseriti nell'editor",
|
|
1363
|
+
INSERT_ERROR: "Nessun blocco describe() valido trovato nel file"
|
|
1175
1364
|
},
|
|
1176
1365
|
RECORDER: {
|
|
1177
1366
|
FS_TITLE: "\u{1F4C1} Accesso ai file",
|
|
@@ -1200,7 +1389,12 @@ var I18N_IT = {
|
|
|
1200
1389
|
BADGE_PAUSED: "\u23F8 PAUSA",
|
|
1201
1390
|
BADGE_REC: "\u25CF REC",
|
|
1202
1391
|
STOP_TITLE: "Ferma (Ctrl+R)",
|
|
1203
|
-
START_TITLE: "Registra (Ctrl+R)"
|
|
1392
|
+
START_TITLE: "Registra (Ctrl+R)",
|
|
1393
|
+
SESSION_RESUME_TITLE: "\u23FA Registrazione in corso",
|
|
1394
|
+
SESSION_RESUME_TEXT: "C'\xE8 una registrazione attiva non terminata.",
|
|
1395
|
+
SESSION_RESUME_COUNT: "comandi catturati",
|
|
1396
|
+
SESSION_CONTINUE_BTN: "Continua a registrare",
|
|
1397
|
+
SESSION_DISCARD_BTN: "Annulla"
|
|
1204
1398
|
}
|
|
1205
1399
|
};
|
|
1206
1400
|
|
|
@@ -1226,7 +1420,9 @@ var I18N_DE = {
|
|
|
1226
1420
|
NO_TAGS: "Keine Tags",
|
|
1227
1421
|
SAVE_BTN: "\u{1F4BE} Speichern",
|
|
1228
1422
|
SAVE_AND_EDIT: "\u{1F4DD} Speichern und bearbeiten",
|
|
1229
|
-
REMOVE_TAG_TITLE: "Entfernen"
|
|
1423
|
+
REMOVE_TAG_TITLE: "Entfernen",
|
|
1424
|
+
NOTES_LABEL: "Beschreibung / Notizen (optional):",
|
|
1425
|
+
NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw."
|
|
1230
1426
|
},
|
|
1231
1427
|
TEST_EDITOR: {
|
|
1232
1428
|
NO_TAGS: "Keine Tags",
|
|
@@ -1236,8 +1432,6 @@ var I18N_DE = {
|
|
|
1236
1432
|
COPY_DESCRIBE: "\u{1F5C2} describe() kopieren",
|
|
1237
1433
|
MULTI_SELECT: "\u{1F5C2} Mehrfachauswahl",
|
|
1238
1434
|
CANCEL_SELECT: "\u2715 Abbrechen",
|
|
1239
|
-
SECTION_COMMANDS: "Befehle",
|
|
1240
|
-
SECTION_INTERCEPTORS: "Interceptors",
|
|
1241
1435
|
COPY_CMDS_BTN: "\u{1F4CB} Befehle kopieren",
|
|
1242
1436
|
COPY_ICPS_BTN: "\u{1F4CB} Interceptors kopieren",
|
|
1243
1437
|
DEFAULT_DESCRIBE: "Test-Suite",
|
|
@@ -1266,6 +1460,18 @@ var I18N_DE = {
|
|
|
1266
1460
|
DATA_SECTION: "\u{1F4BE} Daten",
|
|
1267
1461
|
DATA_DESC: "Exportieren Sie alle Tests als JSON oder importieren Sie ein Backup.",
|
|
1268
1462
|
EXPORT_BTN: "\u2B06\uFE0F Tests exportieren",
|
|
1463
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Tests exportieren",
|
|
1464
|
+
EXPORT_MODE_ALL: "Alle",
|
|
1465
|
+
EXPORT_MODE_MANUAL: "Manuelle Auswahl",
|
|
1466
|
+
EXPORT_MODE_TAGS: "Nach Tags",
|
|
1467
|
+
EXPORT_ALL_DESC: "Alle gespeicherten Tests werden exportiert.",
|
|
1468
|
+
EXPORT_COUNT: "Zu exportieren:",
|
|
1469
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exportieren",
|
|
1470
|
+
EXPORT_CANCEL: "Abbrechen",
|
|
1471
|
+
EXPORT_EMPTY: "Keine Tests zum Exportieren.",
|
|
1472
|
+
EXPORT_NO_TAGS: "Keine Tags verf\xFCgbar.",
|
|
1473
|
+
EXPORT_TAGS_HINT: "W\xE4hle ein oder mehrere Tags, um zu sehen, welche Tests exportiert werden.",
|
|
1474
|
+
EXPORT_RESULT_LABEL: "Diese Tests werden exportiert:",
|
|
1269
1475
|
IMPORT_BTN: "\u2B07\uFE0F Tests importieren",
|
|
1270
1476
|
FOLDER_UPDATED_TOAST: "\u2713 Cypress-Ordner aktualisiert",
|
|
1271
1477
|
FOLDER_ERROR_TOAST: "Fehler beim Zugriff auf den Ordner",
|
|
@@ -1275,7 +1481,13 @@ var I18N_DE = {
|
|
|
1275
1481
|
JSON_BAD_FORMAT: "Die Datei hat nicht das erwartete Format.",
|
|
1276
1482
|
SMART_SELECTOR_SECTION: "\u{1F50D} Intelligenter Selektor",
|
|
1277
1483
|
SMART_SELECTOR_TITLE: "Intelligenter Selektor",
|
|
1278
|
-
SMART_SELECTOR_SUB: "Zeigt ein Auswahlfeld, wenn auf Elemente ohne g\xFCltigen Selektor geklickt wird."
|
|
1484
|
+
SMART_SELECTOR_SUB: "Zeigt ein Auswahlfeld, wenn auf Elemente ohne g\xFCltigen Selektor geklickt wird.",
|
|
1485
|
+
START_HIDDEN_SECTION: "\u{1F441} Widget-Sichtbarkeit",
|
|
1486
|
+
START_HIDDEN_TITLE: "Versteckt starten",
|
|
1487
|
+
START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden.",
|
|
1488
|
+
RESUME_TTL_SECTION: "\u23F1 Aufzeichnungskontinuit\xE4t",
|
|
1489
|
+
RESUME_TTL_LABEL: "Ohne Nachfrage fortsetzen (Minuten)",
|
|
1490
|
+
RESUME_TTL_HINT: "Wenn Sie neu laden oder das Projekt wechseln, wird eine aktive Aufzeichnung innerhalb dieses Zeitfensters ohne Nachfrage fortgesetzt. Danach werden Sie gefragt, ob fortsetzen oder verwerfen."
|
|
1279
1491
|
},
|
|
1280
1492
|
SELECTOR_PICKER: {
|
|
1281
1493
|
TITLE: "DOM-Element ausw\xE4hlen",
|
|
@@ -1316,8 +1528,10 @@ var I18N_DE = {
|
|
|
1316
1528
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Manuell bearbeiten",
|
|
1317
1529
|
CLOSE_BTN: "\u2715 Schlie\xDFen",
|
|
1318
1530
|
NEW_FILE_BTN: "+ Neue Datei",
|
|
1531
|
+
NEW_FOLDER_BTN: "+ Neuer Ordner",
|
|
1319
1532
|
REFRESH_BTN: "\u21BB Aktualisieren",
|
|
1320
1533
|
NEW_FILE_PLACEHOLDER: "test-name",
|
|
1534
|
+
NEW_FOLDER_PLACEHOLDER: "ordnername",
|
|
1321
1535
|
NEW_FILE_CONFIRM: "Erstellen",
|
|
1322
1536
|
NEW_FILE_CANCEL: "Abbrechen"
|
|
1323
1537
|
},
|
|
@@ -1330,10 +1544,19 @@ var I18N_DE = {
|
|
|
1330
1544
|
NO_CHANGES: "Keine \xC4nderungen gegen\xFCber dem Original",
|
|
1331
1545
|
NO_CHANGES_SHORT: "Keine \xC4nderungen",
|
|
1332
1546
|
LAUNCH_BTN: "\u25B6 Test starten",
|
|
1547
|
+
LAUNCH_LOCAL_ONLY: "Wechsle zu localhost, um Tests auszuf\xFChren",
|
|
1548
|
+
LAUNCH_RUNNING: "\u23F3 L\xE4uft\u2026",
|
|
1549
|
+
LAUNCH_PASSED: "\u2713 Test bestanden",
|
|
1550
|
+
LAUNCH_FAILED: "\u2717 Test fehlgeschlagen",
|
|
1551
|
+
LAUNCH_NO_RUNNER: "Kein lokaler Runner erkannt (gestartet?)",
|
|
1333
1552
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1334
1553
|
SAVE_BTN: "\u{1F4BE} Speichern",
|
|
1335
1554
|
CLOSE_BTN: "\u2715 Schlie\xDFen",
|
|
1336
|
-
BACK_TO_EDITOR: "\u2190 Zur\xFCck zum Editor"
|
|
1555
|
+
BACK_TO_EDITOR: "\u2190 Zur\xFCck zum Editor",
|
|
1556
|
+
INSERT_BTN: "\u{1FA84} Bl\xF6cke einf\xFCgen",
|
|
1557
|
+
INSERT_TITLE: "it() und beforeEach() in den Editor-Inhalt einf\xFCgen",
|
|
1558
|
+
INSERT_DONE: "Bl\xF6cke in den Editor eingef\xFCgt",
|
|
1559
|
+
INSERT_ERROR: "Kein g\xFCltiger describe()-Block in der Datei gefunden"
|
|
1337
1560
|
},
|
|
1338
1561
|
RECORDER: {
|
|
1339
1562
|
FS_TITLE: "\u{1F4C1} Dateizugriff",
|
|
@@ -1362,13 +1585,18 @@ var I18N_DE = {
|
|
|
1362
1585
|
BADGE_PAUSED: "\u23F8 PAUSE",
|
|
1363
1586
|
BADGE_REC: "\u25CF REC",
|
|
1364
1587
|
STOP_TITLE: "Stopp (Ctrl+R)",
|
|
1365
|
-
START_TITLE: "Aufzeichnen (Ctrl+R)"
|
|
1588
|
+
START_TITLE: "Aufzeichnen (Ctrl+R)",
|
|
1589
|
+
SESSION_RESUME_TITLE: "\u23FA Aufzeichnung l\xE4uft",
|
|
1590
|
+
SESSION_RESUME_TEXT: "Es gibt eine nicht abgeschlossene aktive Aufzeichnung.",
|
|
1591
|
+
SESSION_RESUME_COUNT: "erfasste Befehle",
|
|
1592
|
+
SESSION_CONTINUE_BTN: "Weiter aufzeichnen",
|
|
1593
|
+
SESSION_DISCARD_BTN: "Verwerfen"
|
|
1366
1594
|
}
|
|
1367
1595
|
};
|
|
1368
1596
|
|
|
1369
1597
|
// src/services/translation.service.ts
|
|
1370
1598
|
var TranslationService = class {
|
|
1371
|
-
lang
|
|
1599
|
+
lang$;
|
|
1372
1600
|
translations = {
|
|
1373
1601
|
es: I18N_ES,
|
|
1374
1602
|
en: I18N_EN,
|
|
@@ -1377,17 +1605,21 @@ var TranslationService = class {
|
|
|
1377
1605
|
de: I18N_DE
|
|
1378
1606
|
};
|
|
1379
1607
|
constructor() {
|
|
1380
|
-
this.lang = this.detectLang();
|
|
1608
|
+
this.lang$ = new Subject(this.detectLang());
|
|
1381
1609
|
}
|
|
1382
1610
|
setLang(lang) {
|
|
1383
|
-
this.lang
|
|
1611
|
+
this.lang$.next(lang);
|
|
1384
1612
|
}
|
|
1385
1613
|
getLang() {
|
|
1386
|
-
return this.lang;
|
|
1614
|
+
return this.lang$.getValue();
|
|
1615
|
+
}
|
|
1616
|
+
/** Subscribe to language changes; returns an unsubscribe function. */
|
|
1617
|
+
onLangChange(fn) {
|
|
1618
|
+
return this.lang$.subscribe(fn);
|
|
1387
1619
|
}
|
|
1388
1620
|
translate(key) {
|
|
1389
1621
|
const keys = key.split(".");
|
|
1390
|
-
let value = this.translations[this.lang];
|
|
1622
|
+
let value = this.translations[this.lang$.getValue()];
|
|
1391
1623
|
for (const k of keys) {
|
|
1392
1624
|
value = value?.[k];
|
|
1393
1625
|
if (value === void 0) return key;
|
|
@@ -1401,14 +1633,41 @@ var TranslationService = class {
|
|
|
1401
1633
|
};
|
|
1402
1634
|
var translationService = new TranslationService();
|
|
1403
1635
|
|
|
1636
|
+
// src/utils/code-format.utils.ts
|
|
1637
|
+
function gcd(a, b) {
|
|
1638
|
+
while (b) {
|
|
1639
|
+
const t = b;
|
|
1640
|
+
b = a % b;
|
|
1641
|
+
a = t;
|
|
1642
|
+
}
|
|
1643
|
+
return a;
|
|
1644
|
+
}
|
|
1645
|
+
function escapeSingleQuotes(value) {
|
|
1646
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1647
|
+
}
|
|
1648
|
+
function normalizeBlock(code, baseIndent) {
|
|
1649
|
+
const lines = code.split("\n").map((l) => l.trimEnd());
|
|
1650
|
+
const nonEmpty = lines.filter((l) => l.trim().length > 0);
|
|
1651
|
+
if (!nonEmpty.length) return "";
|
|
1652
|
+
const indents = nonEmpty.map((l) => l.length - l.trimStart().length);
|
|
1653
|
+
const minIndent = Math.min(...indents);
|
|
1654
|
+
const relIndents = [...new Set(indents.map((n) => n - minIndent).filter((n) => n > 0))];
|
|
1655
|
+
const step = relIndents.length > 0 ? relIndents.reduce(gcd) : 2;
|
|
1656
|
+
return lines.map((l) => {
|
|
1657
|
+
if (!l.trim()) return "";
|
|
1658
|
+
const level = Math.round((l.length - l.trimStart().length - minIndent) / step);
|
|
1659
|
+
return baseIndent + " ".repeat(level) + l.trimStart();
|
|
1660
|
+
}).join("\n");
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1404
1663
|
// src/services/transformation.service.ts
|
|
1405
1664
|
var TransformationService = class {
|
|
1406
1665
|
toLang(lang) {
|
|
1407
1666
|
return isLang(lang) ? lang : "en";
|
|
1408
1667
|
}
|
|
1409
1668
|
generateItDescription(description, commands) {
|
|
1410
|
-
const commandsBlock = commands.map((cmd) =>
|
|
1411
|
-
return `it('${description}', () => {
|
|
1669
|
+
const commandsBlock = commands.map((cmd) => normalizeBlock(cmd, " ")).join("\n");
|
|
1670
|
+
return `it('${escapeSingleQuotes(description)}', () => {
|
|
1412
1671
|
${commandsBlock}
|
|
1413
1672
|
});`;
|
|
1414
1673
|
}
|
|
@@ -1438,6 +1697,13 @@ ${interceptors} })
|
|
|
1438
1697
|
}
|
|
1439
1698
|
return content.slice(0, idx) + "\n" + itBlock + "\n" + content.slice(idx);
|
|
1440
1699
|
}
|
|
1700
|
+
buildBlockComment(notes) {
|
|
1701
|
+
if (!notes.trim()) return "";
|
|
1702
|
+
const lines = notes.split("\n").map((l) => ` * ${l}`).join("\n");
|
|
1703
|
+
return `/**
|
|
1704
|
+
${lines}
|
|
1705
|
+
*/`;
|
|
1706
|
+
}
|
|
1441
1707
|
isFile(file) {
|
|
1442
1708
|
return !!file && file.kind === "file";
|
|
1443
1709
|
}
|
|
@@ -1455,29 +1721,15 @@ ${interceptors} })
|
|
|
1455
1721
|
};
|
|
1456
1722
|
var advancedTestTransformationService = new AdvancedTestTransformationService();
|
|
1457
1723
|
|
|
1458
|
-
// src/utils/subject.ts
|
|
1459
|
-
var Subject = class {
|
|
1460
|
-
_value;
|
|
1461
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1462
|
-
constructor(initialValue) {
|
|
1463
|
-
this._value = initialValue;
|
|
1464
|
-
}
|
|
1465
|
-
next(value) {
|
|
1466
|
-
this._value = value;
|
|
1467
|
-
this.listeners.forEach((l) => l(value));
|
|
1468
|
-
}
|
|
1469
|
-
getValue() {
|
|
1470
|
-
return this._value;
|
|
1471
|
-
}
|
|
1472
|
-
subscribe(fn) {
|
|
1473
|
-
this.listeners.add(fn);
|
|
1474
|
-
return () => this.listeners.delete(fn);
|
|
1475
|
-
}
|
|
1476
|
-
};
|
|
1477
|
-
|
|
1478
1724
|
// src/services/recording.service.ts
|
|
1479
1725
|
init_selector_quality_utils();
|
|
1480
1726
|
var OWN_SELECTOR = '[data-cy="lib-e2e-cypress-for-dummys"]';
|
|
1727
|
+
function createSessionId() {
|
|
1728
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1729
|
+
return crypto.randomUUID();
|
|
1730
|
+
}
|
|
1731
|
+
return `sess-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;
|
|
1732
|
+
}
|
|
1481
1733
|
var RecordingService = class {
|
|
1482
1734
|
commands$ = new Subject([]);
|
|
1483
1735
|
interceptors$ = new Subject([]);
|
|
@@ -1487,6 +1739,9 @@ var RecordingService = class {
|
|
|
1487
1739
|
inputDebounceTimers = /* @__PURE__ */ new Map();
|
|
1488
1740
|
abort = new AbortController();
|
|
1489
1741
|
selectorStrategy = "data-cy";
|
|
1742
|
+
/** Stable id of the current live session (null when none has started). */
|
|
1743
|
+
sessionId = null;
|
|
1744
|
+
startedAt = 0;
|
|
1490
1745
|
// Stored originals for history patching cleanup
|
|
1491
1746
|
origPushState = history.pushState.bind(history);
|
|
1492
1747
|
origReplaceState = history.replaceState.bind(history);
|
|
@@ -1498,12 +1753,42 @@ var RecordingService = class {
|
|
|
1498
1753
|
}
|
|
1499
1754
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
1500
1755
|
startRecording() {
|
|
1756
|
+
this.sessionId = createSessionId();
|
|
1757
|
+
this.startedAt = Date.now();
|
|
1501
1758
|
this.isPaused$.next(false);
|
|
1502
1759
|
this.isRecording$.next(true);
|
|
1503
1760
|
this.addCommand(`cy.viewport(1900, 1200)`);
|
|
1504
1761
|
this.addCommand(`cy.visit('${window.location.pathname}')`);
|
|
1505
1762
|
this.addCommand(`cy.get('[data-cy="lib-e2e-cypress-for-dummys"]').invoke('hide');`);
|
|
1506
1763
|
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Rehydrates a previously persisted session WITHOUT running the startRecording
|
|
1766
|
+
* bootstrap (no viewport/visit/hide). Used to continue a recording across a
|
|
1767
|
+
* micro-frontend crossing or a same-origin reload.
|
|
1768
|
+
* See docs/specs/006-cross-app-recording-continuity.md.
|
|
1769
|
+
*/
|
|
1770
|
+
restoreSession(state) {
|
|
1771
|
+
this.sessionId = state.sessionId;
|
|
1772
|
+
this.startedAt = state.startedAt;
|
|
1773
|
+
this.selectorStrategy = state.selectorStrategy;
|
|
1774
|
+
this.commands$.next([...state.commands]);
|
|
1775
|
+
this.interceptors$.next([...state.interceptors]);
|
|
1776
|
+
this.isPaused$.next(state.isPaused);
|
|
1777
|
+
this.isRecording$.next(state.isRecording);
|
|
1778
|
+
}
|
|
1779
|
+
/** Full snapshot of the live session for persistence. */
|
|
1780
|
+
getSessionSnapshot() {
|
|
1781
|
+
return {
|
|
1782
|
+
sessionId: this.sessionId ?? createSessionId(),
|
|
1783
|
+
isRecording: this.isRecording$.getValue(),
|
|
1784
|
+
isPaused: this.isPaused$.getValue(),
|
|
1785
|
+
commands: this.commands$.getValue(),
|
|
1786
|
+
interceptors: this.interceptors$.getValue(),
|
|
1787
|
+
selectorStrategy: this.selectorStrategy,
|
|
1788
|
+
startedAt: this.startedAt,
|
|
1789
|
+
updatedAt: Date.now()
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1507
1792
|
stopRecording() {
|
|
1508
1793
|
this.isPaused$.next(false);
|
|
1509
1794
|
this.isRecording$.next(false);
|
|
@@ -1590,6 +1875,21 @@ var RecordingService = class {
|
|
|
1590
1875
|
onPauseChange(fn) {
|
|
1591
1876
|
return this.isPaused$.subscribe(fn);
|
|
1592
1877
|
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Fires a full session snapshot whenever any persisted field changes
|
|
1880
|
+
* (commands, interceptors, recording or paused state). Drives the debounced
|
|
1881
|
+
* persistence of the live session. Returns a combined unsubscribe.
|
|
1882
|
+
*/
|
|
1883
|
+
onSessionChange(fn) {
|
|
1884
|
+
const emit = () => fn(this.getSessionSnapshot());
|
|
1885
|
+
const unsubs = [
|
|
1886
|
+
this.commands$.subscribe(emit),
|
|
1887
|
+
this.interceptors$.subscribe(emit),
|
|
1888
|
+
this.isRecording$.subscribe(emit),
|
|
1889
|
+
this.isPaused$.subscribe(emit)
|
|
1890
|
+
];
|
|
1891
|
+
return () => unsubs.forEach((u) => u());
|
|
1892
|
+
}
|
|
1593
1893
|
onSelectorNotFound(fn) {
|
|
1594
1894
|
return this.selectorNotFound$.subscribe((v) => {
|
|
1595
1895
|
if (v) fn(v.target, v.action);
|
|
@@ -1821,6 +2121,7 @@ var RecordingService = class {
|
|
|
1821
2121
|
|
|
1822
2122
|
// src/services/persistence.service.ts
|
|
1823
2123
|
var import_idb = require("idb");
|
|
2124
|
+
var ACTIVE_SESSION_ID = 1;
|
|
1824
2125
|
var PersistenceService = class {
|
|
1825
2126
|
// dbName is overridable so tests can use unique names for isolation.
|
|
1826
2127
|
constructor(dbName = DB_SCHEMA.name) {
|
|
@@ -1849,9 +2150,9 @@ var PersistenceService = class {
|
|
|
1849
2150
|
return this._db;
|
|
1850
2151
|
}
|
|
1851
2152
|
// ── Tests ─────────────────────────────────────────────────────────────────
|
|
1852
|
-
async insertTest(name, commands = [], interceptors = [], tags = []) {
|
|
2153
|
+
async insertTest(name, commands = [], interceptors = [], tags = [], notes) {
|
|
1853
2154
|
const db = await this.getDB();
|
|
1854
|
-
const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {} };
|
|
2155
|
+
const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {}, ...notes ? { notes } : {} };
|
|
1855
2156
|
const id = await db.add("tests", record);
|
|
1856
2157
|
if (commands.length) await this.insertCommands(commands, id);
|
|
1857
2158
|
if (interceptors.length) await this.insertInterceptors(interceptors, id);
|
|
@@ -1874,10 +2175,10 @@ var PersistenceService = class {
|
|
|
1874
2175
|
if (!record) return null;
|
|
1875
2176
|
const commands = await this.getCommandStrings(testId);
|
|
1876
2177
|
const interceptors = await this.getInterceptorStrings(testId);
|
|
1877
|
-
const itBlock = `it('${record.name}', () => {
|
|
1878
|
-
|
|
2178
|
+
const itBlock = `it('${escapeSingleQuotes(record.name)}', () => {
|
|
2179
|
+
${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
1879
2180
|
});`;
|
|
1880
|
-
const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => " "
|
|
2181
|
+
const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
|
|
1881
2182
|
return { ...record, commands, interceptors, cypressCommands: commands, itBlock, interceptorsBlock };
|
|
1882
2183
|
}
|
|
1883
2184
|
async deleteTest(id) {
|
|
@@ -1955,6 +2256,25 @@ var PersistenceService = class {
|
|
|
1955
2256
|
const records = await db.getAll("configuration");
|
|
1956
2257
|
return records[0] ?? null;
|
|
1957
2258
|
}
|
|
2259
|
+
// ── Active recording session ──────────────────────────────────────────────
|
|
2260
|
+
/** Upserts the live recording session (single record, fixed key). */
|
|
2261
|
+
async saveActiveSession(state) {
|
|
2262
|
+
const db = await this.getDB();
|
|
2263
|
+
await db.put("activeSession", { ...state, id: ACTIVE_SESSION_ID });
|
|
2264
|
+
}
|
|
2265
|
+
/** Returns the persisted live session, or null when none is stored. */
|
|
2266
|
+
async getActiveSession() {
|
|
2267
|
+
const db = await this.getDB();
|
|
2268
|
+
const record = await db.get("activeSession", ACTIVE_SESSION_ID);
|
|
2269
|
+
if (!record) return null;
|
|
2270
|
+
const { id: _id, ...state } = record;
|
|
2271
|
+
return state;
|
|
2272
|
+
}
|
|
2273
|
+
/** Removes the live session record. Safe to call when none exists. */
|
|
2274
|
+
async clearActiveSession() {
|
|
2275
|
+
const db = await this.getDB();
|
|
2276
|
+
await db.delete("activeSession", ACTIVE_SESSION_ID);
|
|
2277
|
+
}
|
|
1958
2278
|
// ── Bulk operations ───────────────────────────────────────────────────────
|
|
1959
2279
|
async clearAllData() {
|
|
1960
2280
|
const db = await this.getDB();
|
|
@@ -1965,10 +2285,18 @@ var PersistenceService = class {
|
|
|
1965
2285
|
]);
|
|
1966
2286
|
}
|
|
1967
2287
|
async ingestFileData(tests, interceptors) {
|
|
1968
|
-
|
|
1969
|
-
this.
|
|
1970
|
-
|
|
1971
|
-
|
|
2288
|
+
if (Array.isArray(tests)) {
|
|
2289
|
+
const db = await this.getDB();
|
|
2290
|
+
for (const test of tests) {
|
|
2291
|
+
const { id: _id, commands, interceptors: testInterceptors, ...record } = test;
|
|
2292
|
+
const newId = await db.add("tests", record);
|
|
2293
|
+
const cmds = Array.isArray(commands) ? commands : [];
|
|
2294
|
+
const icps = Array.isArray(testInterceptors) ? testInterceptors : [];
|
|
2295
|
+
if (cmds.length) await this.insertCommands(cmds, newId);
|
|
2296
|
+
if (icps.length) await this.insertInterceptors(icps, newId);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
await this.bulkInsertWithoutId("interceptors", interceptors);
|
|
1972
2300
|
}
|
|
1973
2301
|
async requestDirectoryPermissions() {
|
|
1974
2302
|
if (!("showDirectoryPicker" in window)) {
|
|
@@ -2411,6 +2739,28 @@ function showToast(message, isSuccess = true) {
|
|
|
2411
2739
|
setTimeout(() => toast.remove(), 3e3);
|
|
2412
2740
|
}
|
|
2413
2741
|
|
|
2742
|
+
// src/utils/export-selection.utils.ts
|
|
2743
|
+
function selectTestsForExport(tests, mode, opts = {}) {
|
|
2744
|
+
if (mode === "all") return [...tests];
|
|
2745
|
+
if (mode === "manual") {
|
|
2746
|
+
const ids = new Set(opts.ids ?? []);
|
|
2747
|
+
return tests.filter((t) => ids.has(t.id));
|
|
2748
|
+
}
|
|
2749
|
+
const tags = new Set(opts.tags ?? []);
|
|
2750
|
+
if (tags.size === 0) return [];
|
|
2751
|
+
return tests.filter((t) => (t.tags ?? []).some((tag) => tags.has(tag)));
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/utils/host.utils.ts
|
|
2755
|
+
function isLocalHost(hostname) {
|
|
2756
|
+
if (!hostname) return true;
|
|
2757
|
+
const h = hostname.toLowerCase();
|
|
2758
|
+
return h === "localhost" || h.endsWith(".localhost") || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0";
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
// src/index.ts
|
|
2762
|
+
init_selector_picker();
|
|
2763
|
+
|
|
2414
2764
|
// src/components/test-previsualizer/test-previsualizer.styles.ts
|
|
2415
2765
|
var TEST_PREVISUALIZER_STYLES = `
|
|
2416
2766
|
:host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
|
|
@@ -2617,7 +2967,8 @@ if (!customElements.get("test-previsualizer")) {
|
|
|
2617
2967
|
var SAVE_TEST_STYLES = `
|
|
2618
2968
|
:host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
|
|
2619
2969
|
* { box-sizing: border-box; }
|
|
2620
|
-
.container { padding: 24px 28px; text-align:
|
|
2970
|
+
.container { padding: 24px 28px; text-align: left; }
|
|
2971
|
+
.btn-row, p { text-align: center; }
|
|
2621
2972
|
p { margin: 0 0 20px; font-size: 14px; color: #8b949e; line-height: 1.5; }
|
|
2622
2973
|
.btn-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 16px; }
|
|
2623
2974
|
button {
|
|
@@ -2639,6 +2990,15 @@ var SAVE_TEST_STYLES = `
|
|
|
2639
2990
|
}
|
|
2640
2991
|
input[type="text"]:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.15); }
|
|
2641
2992
|
input[type="text"]::placeholder { color: #484f58; }
|
|
2993
|
+
textarea {
|
|
2994
|
+
width: 100%; padding: 10px 14px; border: 1px solid #30363d;
|
|
2995
|
+
border-radius: 8px; background: #0d1117; color: #e6edf3;
|
|
2996
|
+
font-size: 14px; outline: none; margin-bottom: 4px;
|
|
2997
|
+
font-family: inherit; resize: vertical; line-height: 1.5;
|
|
2998
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
2999
|
+
}
|
|
3000
|
+
textarea:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.15); }
|
|
3001
|
+
textarea::placeholder { color: #484f58; }
|
|
2642
3002
|
.tag-label {
|
|
2643
3003
|
font-size: 11px; color: #484f58; text-align: left; margin-bottom: 5px; display: block;
|
|
2644
3004
|
}
|
|
@@ -2678,7 +3038,7 @@ function renderSaveTestAsk(t) {
|
|
|
2678
3038
|
</div>
|
|
2679
3039
|
</div>`;
|
|
2680
3040
|
}
|
|
2681
|
-
function renderSaveTestDesc(description, tags, t) {
|
|
3041
|
+
function renderSaveTestDesc(description, notes, tags, t) {
|
|
2682
3042
|
const chipsHtml = tags.map(
|
|
2683
3043
|
(tag) => `<span class="chip">${escHtml(tag)}<button class="chip-del" data-tag="${escAttr(tag)}" title="${t("SAVE_TEST.REMOVE_TAG_TITLE")}">\u2715</button></span>`
|
|
2684
3044
|
).join("");
|
|
@@ -2687,6 +3047,8 @@ function renderSaveTestDesc(description, tags, t) {
|
|
|
2687
3047
|
<p>${t("SAVE_TEST.DESC_LABEL")} (<code>it()</code>):</p>
|
|
2688
3048
|
<input id="desc-input" type="text" placeholder="${t("SAVE_TEST.DESC_PLACEHOLDER")}"
|
|
2689
3049
|
value="${escAttr(description)}" autocomplete="off" />
|
|
3050
|
+
<span class="tag-label">${t("SAVE_TEST.NOTES_LABEL")}</span>
|
|
3051
|
+
<textarea id="notes-input" rows="3" placeholder="${escAttr(t("SAVE_TEST.NOTES_PLACEHOLDER"))}">${escHtml(notes)}</textarea>
|
|
2690
3052
|
<span class="tag-label">${t("SAVE_TEST.TAGS_LABEL")}</span>
|
|
2691
3053
|
<div class="tag-input-row">
|
|
2692
3054
|
<input id="tag-input" type="text" placeholder="${t("SAVE_TEST.TAGS_PLACEHOLDER")}" autocomplete="off" />
|
|
@@ -2706,6 +3068,7 @@ var SaveTestElement = class extends HTMLElement {
|
|
|
2706
3068
|
shadow;
|
|
2707
3069
|
_step = "ask";
|
|
2708
3070
|
description = "";
|
|
3071
|
+
notes = "";
|
|
2709
3072
|
tags = [];
|
|
2710
3073
|
translation = translationService;
|
|
2711
3074
|
constructor() {
|
|
@@ -2723,17 +3086,18 @@ var SaveTestElement = class extends HTMLElement {
|
|
|
2723
3086
|
this.render();
|
|
2724
3087
|
}
|
|
2725
3088
|
confirmSave() {
|
|
2726
|
-
this.dispatch("savetest", { description: this.description.trim(), tags: [...this.tags] });
|
|
3089
|
+
this.dispatch("savetest", { description: this.description.trim(), notes: this.notes, tags: [...this.tags] });
|
|
2727
3090
|
}
|
|
2728
3091
|
confirmSaveAndExport() {
|
|
2729
|
-
this.dispatch("saveandexport", { description: this.description.trim(), tags: [...this.tags] });
|
|
3092
|
+
this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags] });
|
|
2730
3093
|
}
|
|
2731
3094
|
cancel() {
|
|
2732
|
-
this.dispatch("savetest", { description: null, tags: [] });
|
|
3095
|
+
this.dispatch("savetest", { description: null, notes: "", tags: [] });
|
|
2733
3096
|
}
|
|
2734
3097
|
restartComponent() {
|
|
2735
3098
|
this._step = "ask";
|
|
2736
3099
|
this.description = "";
|
|
3100
|
+
this.notes = "";
|
|
2737
3101
|
this.tags = [];
|
|
2738
3102
|
this.render();
|
|
2739
3103
|
}
|
|
@@ -2760,12 +3124,16 @@ var SaveTestElement = class extends HTMLElement {
|
|
|
2760
3124
|
this.shadow.getElementById("btn-yes")?.addEventListener("click", () => this.askSave());
|
|
2761
3125
|
this.shadow.getElementById("btn-no")?.addEventListener("click", () => this.cancel());
|
|
2762
3126
|
} else {
|
|
2763
|
-
this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.tags, this.t.bind(this))}`;
|
|
3127
|
+
this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.notes, this.tags, this.t.bind(this))}`;
|
|
2764
3128
|
const descInput = this.shadow.getElementById("desc-input");
|
|
3129
|
+
const notesInput = this.shadow.getElementById("notes-input");
|
|
2765
3130
|
const tagInput = this.shadow.getElementById("tag-input");
|
|
2766
3131
|
descInput.addEventListener("input", () => {
|
|
2767
3132
|
this.description = descInput.value;
|
|
2768
3133
|
});
|
|
3134
|
+
notesInput.addEventListener("input", () => {
|
|
3135
|
+
this.notes = notesInput.value;
|
|
3136
|
+
});
|
|
2769
3137
|
const tryAddTag = () => {
|
|
2770
3138
|
this.addTag(tagInput.value);
|
|
2771
3139
|
tagInput.value = "";
|
|
@@ -2871,19 +3239,131 @@ var TEST_EDITOR_STYLES = `
|
|
|
2871
3239
|
background: #0d1117; padding: 10px 14px;
|
|
2872
3240
|
border-top: 1px solid #21262d;
|
|
2873
3241
|
}
|
|
2874
|
-
.
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
.
|
|
3242
|
+
.test-notes {
|
|
3243
|
+
margin: 0 0 10px; padding: 8px 12px;
|
|
3244
|
+
background: rgba(47,129,247,0.06); border-left: 3px solid rgba(47,129,247,0.4);
|
|
3245
|
+
border-radius: 0 6px 6px 0; font-size: 12px; color: #8b949e;
|
|
3246
|
+
line-height: 1.6; white-space: pre-wrap; word-break: break-word;
|
|
3247
|
+
}
|
|
3248
|
+
.code-preview {
|
|
3249
|
+
margin: 0; padding: 10px 12px; background: #161b22;
|
|
3250
|
+
border-radius: 6px; border: 1px solid #21262d;
|
|
3251
|
+
font-size: 11px; color: #c9d1d9; line-height: 1.7;
|
|
3252
|
+
font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
|
|
3253
|
+
overflow-x: auto; white-space: pre;
|
|
3254
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
3255
|
+
}
|
|
3256
|
+
.code-preview::-webkit-scrollbar { height: 4px; }
|
|
3257
|
+
.code-preview::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
|
|
3258
|
+
.code-preview-icp { margin-top: 8px; }
|
|
3259
|
+
.sh-kw { color: #ff7b72; }
|
|
3260
|
+
.sh-bi { color: #d2a8ff; }
|
|
3261
|
+
.sh-str { color: #a5d6ff; }
|
|
3262
|
+
.sh-cmt { color: #8b949e; font-style: italic; }
|
|
3263
|
+
.sh-num { color: #79c0ff; }
|
|
2881
3264
|
`;
|
|
2882
3265
|
|
|
2883
3266
|
// src/components/test-editor/test-editor.template.ts
|
|
2884
3267
|
init_html_utils();
|
|
3268
|
+
|
|
3269
|
+
// src/utils/syntax-highlight.utils.ts
|
|
3270
|
+
init_html_utils();
|
|
3271
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
3272
|
+
"describe",
|
|
3273
|
+
"it",
|
|
3274
|
+
"xit",
|
|
3275
|
+
"fit",
|
|
3276
|
+
"xdescribe",
|
|
3277
|
+
"fdescribe",
|
|
3278
|
+
"beforeEach",
|
|
3279
|
+
"afterEach",
|
|
3280
|
+
"before",
|
|
3281
|
+
"after",
|
|
3282
|
+
"context",
|
|
3283
|
+
"const",
|
|
3284
|
+
"let",
|
|
3285
|
+
"var",
|
|
3286
|
+
"function",
|
|
3287
|
+
"return",
|
|
3288
|
+
"if",
|
|
3289
|
+
"else",
|
|
3290
|
+
"import",
|
|
3291
|
+
"export",
|
|
3292
|
+
"from",
|
|
3293
|
+
"new",
|
|
3294
|
+
"this",
|
|
3295
|
+
"null",
|
|
3296
|
+
"undefined",
|
|
3297
|
+
"true",
|
|
3298
|
+
"false",
|
|
3299
|
+
"async",
|
|
3300
|
+
"await",
|
|
3301
|
+
"of",
|
|
3302
|
+
"in",
|
|
3303
|
+
"typeof",
|
|
3304
|
+
"void"
|
|
3305
|
+
]);
|
|
3306
|
+
var BUILTINS = /* @__PURE__ */ new Set(["cy", "expect", "assert", "Cypress", "chai"]);
|
|
3307
|
+
function syntaxHighlight(code) {
|
|
3308
|
+
return code.split("\n").map(highlightLine).join("\n");
|
|
3309
|
+
}
|
|
3310
|
+
function highlightLine(line) {
|
|
3311
|
+
let result = "";
|
|
3312
|
+
let i = 0;
|
|
3313
|
+
const len = line.length;
|
|
3314
|
+
while (i < len) {
|
|
3315
|
+
if (line[i] === "/" && i + 1 < len && line[i + 1] === "/") {
|
|
3316
|
+
result += `<span class="sh-cmt">${escHtml(line.slice(i))}</span>`;
|
|
3317
|
+
return result;
|
|
3318
|
+
}
|
|
3319
|
+
const q = line[i];
|
|
3320
|
+
if (q === "'" || q === '"' || q === "`") {
|
|
3321
|
+
let j = i + 1;
|
|
3322
|
+
while (j < len) {
|
|
3323
|
+
if (line[j] === "\\") {
|
|
3324
|
+
j += 2;
|
|
3325
|
+
continue;
|
|
3326
|
+
}
|
|
3327
|
+
if (line[j] === q) {
|
|
3328
|
+
j++;
|
|
3329
|
+
break;
|
|
3330
|
+
}
|
|
3331
|
+
j++;
|
|
3332
|
+
}
|
|
3333
|
+
result += `<span class="sh-str">${escHtml(line.slice(i, j))}</span>`;
|
|
3334
|
+
i = j;
|
|
3335
|
+
continue;
|
|
3336
|
+
}
|
|
3337
|
+
if (/[0-9]/.test(line[i]) && (i === 0 || !/[a-zA-Z_$]/.test(line[i - 1]))) {
|
|
3338
|
+
let j = i;
|
|
3339
|
+
while (j < len && /[0-9.]/.test(line[j])) j++;
|
|
3340
|
+
result += `<span class="sh-num">${escHtml(line.slice(i, j))}</span>`;
|
|
3341
|
+
i = j;
|
|
3342
|
+
continue;
|
|
3343
|
+
}
|
|
3344
|
+
if (/[a-zA-Z_$]/.test(line[i])) {
|
|
3345
|
+
let j = i;
|
|
3346
|
+
while (j < len && /[a-zA-Z0-9_$]/.test(line[j])) j++;
|
|
3347
|
+
const word = line.slice(i, j);
|
|
3348
|
+
if (KEYWORDS.has(word)) {
|
|
3349
|
+
result += `<span class="sh-kw">${escHtml(word)}</span>`;
|
|
3350
|
+
} else if (BUILTINS.has(word)) {
|
|
3351
|
+
result += `<span class="sh-bi">${escHtml(word)}</span>`;
|
|
3352
|
+
} else {
|
|
3353
|
+
result += escHtml(word);
|
|
3354
|
+
}
|
|
3355
|
+
i = j;
|
|
3356
|
+
continue;
|
|
3357
|
+
}
|
|
3358
|
+
result += escHtml(line[i]);
|
|
3359
|
+
i++;
|
|
3360
|
+
}
|
|
3361
|
+
return result;
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
// src/components/test-editor/test-editor.template.ts
|
|
2885
3365
|
function renderTestEditor(state, t) {
|
|
2886
|
-
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest } = state;
|
|
3366
|
+
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest, locale } = state;
|
|
2887
3367
|
const tagFilterHtml = tags.length ? `<div class="tag-filter">
|
|
2888
3368
|
${tags.map((tag) => `<button class="tag-chip${activeTag === tag ? " active" : ""}" data-filter-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}
|
|
2889
3369
|
</div>` : `<div class="tag-filter" style="color:#484f58;font-size:11px">${t("TEST_EDITOR.NO_TAGS")}</div>`;
|
|
@@ -2894,22 +3374,24 @@ function renderTestEditor(state, t) {
|
|
|
2894
3374
|
</div>` : "";
|
|
2895
3375
|
const rows = visible.map((test, i) => {
|
|
2896
3376
|
const expanded = expandedIndex === i;
|
|
2897
|
-
const date = new Date(test.createdAt).toLocaleString(
|
|
3377
|
+
const date = new Date(test.createdAt).toLocaleString(locale, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
2898
3378
|
const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
|
|
2899
3379
|
const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
|
|
2900
3380
|
const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
|
|
3381
|
+
const hasIcps = (test.interceptors ?? []).length > 0;
|
|
3382
|
+
const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
|
|
3383
|
+
${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
3384
|
+
});`;
|
|
3385
|
+
const icpBlockCode = icps.length ? `beforeEach(() => {
|
|
3386
|
+
// Auto-generated Cypress interceptors
|
|
3387
|
+
${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
3388
|
+
});` : "";
|
|
3389
|
+
const notesHtml = expanded && test.notes ? `<p class="test-notes">${escHtml(test.notes)}</p>` : "";
|
|
2901
3390
|
const body = expanded ? `
|
|
2902
3391
|
<div class="row-body">
|
|
2903
|
-
|
|
2904
|
-
<
|
|
2905
|
-
${
|
|
2906
|
-
<div class="section-title" style="margin-top:8px">${t("TEST_EDITOR.SECTION_INTERCEPTORS")}</div>
|
|
2907
|
-
${icps.map(escHtml).join("<br>")}
|
|
2908
|
-
</div>` : ""}
|
|
2909
|
-
<div class="copy-row">
|
|
2910
|
-
<button class="btn-icon" data-action="copy-cmds" data-idx="${i}">${t("TEST_EDITOR.COPY_CMDS_BTN")}</button>
|
|
2911
|
-
${icps.length ? `<button class="btn-icon" data-action="copy-icps" data-idx="${i}">${t("TEST_EDITOR.COPY_ICPS_BTN")}</button>` : ""}
|
|
2912
|
-
</div>
|
|
3392
|
+
${notesHtml}
|
|
3393
|
+
<pre class="code-preview">${syntaxHighlight(itBlockCode)}</pre>
|
|
3394
|
+
${icpBlockCode ? `<pre class="code-preview code-preview-icp">${syntaxHighlight(icpBlockCode)}</pre>` : ""}
|
|
2913
3395
|
</div>` : "";
|
|
2914
3396
|
return `
|
|
2915
3397
|
<div class="row${selectedIds.has(test.id) ? " selected-row" : ""}">
|
|
@@ -2918,6 +3400,8 @@ function renderTestEditor(state, t) {
|
|
|
2918
3400
|
<span class="test-name">${escHtml(test.name)}</span>
|
|
2919
3401
|
${tagsHtml}
|
|
2920
3402
|
<span class="test-date">${date}</span>
|
|
3403
|
+
<button class="btn-icon" data-action="copy-cmds" data-idx="${i}" title="${t("TEST_EDITOR.COPY_CMDS_BTN")}">${t("TEST_EDITOR.COPY_CMDS_BTN")}</button>
|
|
3404
|
+
${hasIcps ? `<button class="btn-icon" data-action="copy-icps" data-idx="${i}" title="${t("TEST_EDITOR.COPY_ICPS_BTN")}">${t("TEST_EDITOR.COPY_ICPS_BTN")}</button>` : ""}
|
|
2921
3405
|
<button class="btn-icon btn-del" data-action="delete" data-id="${test.id}" title="${t("TEST_EDITOR.DELETE_TITLE")}">\u{1F5D1}</button>
|
|
2922
3406
|
</div>
|
|
2923
3407
|
${body}
|
|
@@ -3003,11 +3487,11 @@ var TestEditorElement = class extends HTMLElement {
|
|
|
3003
3487
|
` : "";
|
|
3004
3488
|
const itBlocks = selected.map((t) => {
|
|
3005
3489
|
const cmds = (t.commands ?? []).map((c) => ` ${c}`).join("\n");
|
|
3006
|
-
return ` it('${(t.name ?? "")
|
|
3490
|
+
return ` it('${escapeSingleQuotes(t.name ?? "")}', () => {
|
|
3007
3491
|
${cmds}
|
|
3008
3492
|
});`;
|
|
3009
3493
|
}).join("\n\n");
|
|
3010
|
-
const block = `describe('${name
|
|
3494
|
+
const block = `describe('${escapeSingleQuotes(name)}', () => {
|
|
3011
3495
|
${beforeEach}${itBlocks}
|
|
3012
3496
|
});`;
|
|
3013
3497
|
navigator.clipboard?.writeText(block);
|
|
@@ -3041,7 +3525,8 @@ ${beforeEach}${itBlocks}
|
|
|
3041
3525
|
selectedIds: this.selectedIds,
|
|
3042
3526
|
describeName: this.describeName,
|
|
3043
3527
|
expandedIndex: this.expandedIndex,
|
|
3044
|
-
interceptorsByTest: this.interceptorsByTest
|
|
3528
|
+
interceptorsByTest: this.interceptorsByTest,
|
|
3529
|
+
locale: localeForLang(this.translation.getLang())
|
|
3045
3530
|
}, this.t.bind(this))}`;
|
|
3046
3531
|
this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
|
|
3047
3532
|
this.shadow.querySelectorAll("[data-filter-tag]").forEach((el) => {
|
|
@@ -3096,7 +3581,7 @@ ${beforeEach}${itBlocks}
|
|
|
3096
3581
|
ev.stopPropagation();
|
|
3097
3582
|
const idx = Number(el.dataset["idx"]);
|
|
3098
3583
|
const t = visible[idx];
|
|
3099
|
-
this.copyToClipboard((this.interceptorsByTest[t?.id] ?? []).join("\n"));
|
|
3584
|
+
this.copyToClipboard((this.interceptorsByTest[t?.id] ?? t?.interceptors ?? []).join("\n"));
|
|
3100
3585
|
});
|
|
3101
3586
|
});
|
|
3102
3587
|
}
|
|
@@ -3198,9 +3683,75 @@ var CONFIGURATION_STYLES = `
|
|
|
3198
3683
|
|
|
3199
3684
|
/* \u2500\u2500 Data section desc \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
3200
3685
|
.data-desc { font-size: 11px; color: #484f58; margin-bottom: 10px; line-height: 1.5; }
|
|
3686
|
+
|
|
3687
|
+
/* \u2500\u2500 Export overlay (modal) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
3688
|
+
.export-overlay {
|
|
3689
|
+
position: fixed; inset: 0; z-index: 100000;
|
|
3690
|
+
background: rgba(1,4,9,0.7);
|
|
3691
|
+
display: flex; align-items: center; justify-content: center; padding: 20px;
|
|
3692
|
+
}
|
|
3693
|
+
.export-modal {
|
|
3694
|
+
width: 640px; max-width: 100%; max-height: 86vh;
|
|
3695
|
+
display: flex; flex-direction: column;
|
|
3696
|
+
background: #161b22; border: 1px solid #30363d; border-radius: 12px;
|
|
3697
|
+
box-shadow: 0 12px 40px rgba(0,0,0,0.5); overflow: hidden;
|
|
3698
|
+
}
|
|
3699
|
+
.export-hd {
|
|
3700
|
+
padding: 14px 18px; font-size: 13px; font-weight: 700;
|
|
3701
|
+
border-bottom: 1px solid #21262d; color: #e6edf3;
|
|
3702
|
+
}
|
|
3703
|
+
.export-modes { display: flex; gap: 6px; padding: 12px 18px 0; }
|
|
3704
|
+
.export-mode {
|
|
3705
|
+
flex: 1; padding: 7px 10px; font-size: 11px; border-radius: 6px;
|
|
3706
|
+
background: #0d1117; border: 1px solid #30363d; color: #8b949e;
|
|
3707
|
+
}
|
|
3708
|
+
.export-mode:hover { background: #21262d; color: #e6edf3; }
|
|
3709
|
+
.export-mode.active { background: rgba(47,129,247,0.15); border-color: #2f81f7; color: #2f81f7; }
|
|
3710
|
+
.export-body {
|
|
3711
|
+
padding: 14px 18px; overflow-y: auto; flex: 1; min-height: 160px;
|
|
3712
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
3713
|
+
}
|
|
3714
|
+
.export-tag-hint { font-size: 12px; color: #484f58; margin-top: 12px; }
|
|
3715
|
+
.export-result-label {
|
|
3716
|
+
font-size: 10px; color: #484f58; text-transform: uppercase; letter-spacing: 0.6px;
|
|
3717
|
+
font-weight: 600; margin: 14px 0 6px;
|
|
3718
|
+
}
|
|
3719
|
+
.export-row-static { cursor: default; }
|
|
3720
|
+
.export-row-static:hover { background: transparent; }
|
|
3721
|
+
.export-all-desc { font-size: 12px; color: #8b949e; }
|
|
3722
|
+
.export-empty { font-size: 12px; color: #484f58; text-align: center; padding: 20px; }
|
|
3723
|
+
.export-list { display: flex; flex-direction: column; gap: 4px; }
|
|
3724
|
+
.export-row {
|
|
3725
|
+
display: flex; align-items: center; gap: 8px;
|
|
3726
|
+
padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 12px; color: #c9d1d9;
|
|
3727
|
+
}
|
|
3728
|
+
.export-row:hover { background: #0d1117; }
|
|
3729
|
+
.export-row-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3730
|
+
.export-row-tag {
|
|
3731
|
+
font-size: 9px; color: #8b949e; background: #0d1117;
|
|
3732
|
+
border: 1px solid #30363d; border-radius: 10px; padding: 1px 7px; flex-shrink: 0;
|
|
3733
|
+
}
|
|
3734
|
+
.export-tags { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
3735
|
+
.export-tag {
|
|
3736
|
+
padding: 4px 10px; font-size: 11px; border-radius: 12px;
|
|
3737
|
+
background: #0d1117; border: 1px solid #30363d; color: #8b949e;
|
|
3738
|
+
}
|
|
3739
|
+
.export-tag:hover { background: #21262d; color: #e6edf3; }
|
|
3740
|
+
.export-tag.active { background: rgba(47,129,247,0.15); border-color: #2f81f7; color: #2f81f7; }
|
|
3741
|
+
.export-ft {
|
|
3742
|
+
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
|
3743
|
+
padding: 12px 18px; border-top: 1px solid #21262d;
|
|
3744
|
+
}
|
|
3745
|
+
.export-count { font-size: 11px; color: #8b949e; }
|
|
3746
|
+
.export-count b { color: #e6edf3; }
|
|
3747
|
+
.export-ft-actions { display: flex; gap: 8px; }
|
|
3748
|
+
.btn-export-confirm { background: #238636; border-color: #238636; color: #fff; }
|
|
3749
|
+
.btn-export-confirm:hover:not(:disabled) { background: #2ea043; border-color: #2ea043; }
|
|
3750
|
+
.btn-export-confirm:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
3201
3751
|
`;
|
|
3202
3752
|
|
|
3203
3753
|
// src/components/configuration/configuration.template.ts
|
|
3754
|
+
init_html_utils();
|
|
3204
3755
|
var LANGS = [
|
|
3205
3756
|
{ value: "es", label: "Espa\xF1ol" },
|
|
3206
3757
|
{ value: "en", label: "English" },
|
|
@@ -3208,8 +3759,64 @@ var LANGS = [
|
|
|
3208
3759
|
{ value: "it", label: "Italiano" },
|
|
3209
3760
|
{ value: "de", label: "Deutsch" }
|
|
3210
3761
|
];
|
|
3762
|
+
function renderExportOverlay(state, t) {
|
|
3763
|
+
const { isExporting, exportMode, exportTests, exportSelectedIds, exportSelectedTags } = state;
|
|
3764
|
+
if (!isExporting) return "";
|
|
3765
|
+
const count = selectTestsForExport(exportTests, exportMode, {
|
|
3766
|
+
ids: exportSelectedIds,
|
|
3767
|
+
tags: exportSelectedTags
|
|
3768
|
+
}).length;
|
|
3769
|
+
const modeBtn = (mode, key) => `<button class="export-mode ${exportMode === mode ? "active" : ""}" data-export-mode="${mode}">${t(key)}</button>`;
|
|
3770
|
+
let body;
|
|
3771
|
+
if (exportTests.length === 0) {
|
|
3772
|
+
body = `<div class="export-empty">${t("CONFIG.EXPORT_EMPTY")}</div>`;
|
|
3773
|
+
} else if (exportMode === "all") {
|
|
3774
|
+
body = `<div class="export-all-desc">${t("CONFIG.EXPORT_ALL_DESC")}</div>`;
|
|
3775
|
+
} else if (exportMode === "manual") {
|
|
3776
|
+
body = `<div class="export-list">${exportTests.map((test) => `
|
|
3777
|
+
<label class="export-row">
|
|
3778
|
+
<input type="checkbox" data-export-test="${test.id}" ${exportSelectedIds.has(test.id) ? "checked" : ""} />
|
|
3779
|
+
<span class="export-row-name">${escHtml(test.name)}</span>
|
|
3780
|
+
${(test.tags ?? []).map((tag) => `<span class="export-row-tag">${escHtml(tag)}</span>`).join("")}
|
|
3781
|
+
</label>`).join("")}</div>`;
|
|
3782
|
+
} else {
|
|
3783
|
+
const allTags = [...new Set(exportTests.flatMap((test) => test.tags ?? []))].sort();
|
|
3784
|
+
if (!allTags.length) {
|
|
3785
|
+
body = `<div class="export-empty">${t("CONFIG.EXPORT_NO_TAGS")}</div>`;
|
|
3786
|
+
} else {
|
|
3787
|
+
const chips = `<div class="export-tags">${allTags.map((tag) => `<button class="export-tag ${exportSelectedTags.has(tag) ? "active" : ""}" data-export-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}</div>`;
|
|
3788
|
+
const matched = selectTestsForExport(exportTests, "tags", { tags: exportSelectedTags });
|
|
3789
|
+
const preview = exportSelectedTags.size === 0 ? `<div class="export-tag-hint">${t("CONFIG.EXPORT_TAGS_HINT")}</div>` : `<div class="export-result-label">${t("CONFIG.EXPORT_RESULT_LABEL")}</div>
|
|
3790
|
+
<div class="export-list">${matched.map((test) => `
|
|
3791
|
+
<div class="export-row export-row-static">
|
|
3792
|
+
<span class="export-row-name">${escHtml(test.name)}</span>
|
|
3793
|
+
${(test.tags ?? []).map((tag) => `<span class="export-row-tag">${escHtml(tag)}</span>`).join("")}
|
|
3794
|
+
</div>`).join("")}</div>`;
|
|
3795
|
+
body = chips + preview;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
return `
|
|
3799
|
+
<div class="export-overlay" id="export-overlay">
|
|
3800
|
+
<div class="export-modal">
|
|
3801
|
+
<div class="export-hd">${t("CONFIG.EXPORT_DIALOG_TITLE")}</div>
|
|
3802
|
+
${exportTests.length ? `<div class="export-modes">
|
|
3803
|
+
${modeBtn("all", "CONFIG.EXPORT_MODE_ALL")}
|
|
3804
|
+
${modeBtn("manual", "CONFIG.EXPORT_MODE_MANUAL")}
|
|
3805
|
+
${modeBtn("tags", "CONFIG.EXPORT_MODE_TAGS")}
|
|
3806
|
+
</div>` : ""}
|
|
3807
|
+
<div class="export-body">${body}</div>
|
|
3808
|
+
<div class="export-ft">
|
|
3809
|
+
<span class="export-count">${t("CONFIG.EXPORT_COUNT")} <b>${count}</b></span>
|
|
3810
|
+
<span class="export-ft-actions">
|
|
3811
|
+
<button id="btn-export-confirm" class="btn-export-confirm" ${count === 0 ? "disabled" : ""}>${t("CONFIG.EXPORT_CONFIRM")}</button>
|
|
3812
|
+
<button id="btn-export-cancel">${t("CONFIG.EXPORT_CANCEL")}</button>
|
|
3813
|
+
</span>
|
|
3814
|
+
</div>
|
|
3815
|
+
</div>
|
|
3816
|
+
</div>`;
|
|
3817
|
+
}
|
|
3211
3818
|
function renderConfiguration(state, t) {
|
|
3212
|
-
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled } = state;
|
|
3819
|
+
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes } = state;
|
|
3213
3820
|
const langOptions = LANGS.map(
|
|
3214
3821
|
(l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
|
|
3215
3822
|
).join("");
|
|
@@ -3249,6 +3856,29 @@ function renderConfiguration(state, t) {
|
|
|
3249
3856
|
</label>
|
|
3250
3857
|
</div>
|
|
3251
3858
|
|
|
3859
|
+
<!-- Start Hidden -->
|
|
3860
|
+
<div class="card">
|
|
3861
|
+
<div class="card-hd">${t("CONFIG.START_HIDDEN_SECTION")}</div>
|
|
3862
|
+
<label class="check-row">
|
|
3863
|
+
<input type="checkbox" id="start-hidden-toggle" ${startHidden ? "checked" : ""} />
|
|
3864
|
+
<div>
|
|
3865
|
+
<div class="check-title">${t("CONFIG.START_HIDDEN_TITLE")}</div>
|
|
3866
|
+
<div class="check-sub">${t("CONFIG.START_HIDDEN_SUB")}</div>
|
|
3867
|
+
</div>
|
|
3868
|
+
</label>
|
|
3869
|
+
</div>
|
|
3870
|
+
|
|
3871
|
+
<!-- Recording continuity (cross-app resume TTL) -->
|
|
3872
|
+
<div class="card">
|
|
3873
|
+
<div class="card-hd">${t("CONFIG.RESUME_TTL_SECTION")}</div>
|
|
3874
|
+
<div class="field-row">
|
|
3875
|
+
<span class="field-label">${t("CONFIG.RESUME_TTL_LABEL")}</span>
|
|
3876
|
+
<input type="number" id="resume-ttl-input" min="1" step="1" value="${resumeTtlMinutes}"
|
|
3877
|
+
style="width:80px;padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;color:#c9d1d9;font-size:12px;outline:none" />
|
|
3878
|
+
</div>
|
|
3879
|
+
<div class="check-sub" style="margin-top:8px">${t("CONFIG.RESUME_TTL_HINT")}</div>
|
|
3880
|
+
</div>
|
|
3881
|
+
|
|
3252
3882
|
<!-- Selector Strategy -->
|
|
3253
3883
|
<div class="card card-wide">
|
|
3254
3884
|
<div class="card-hd">${t("CONFIG.SELECTOR_SECTION")}</div>
|
|
@@ -3268,13 +3898,13 @@ function renderConfiguration(state, t) {
|
|
|
3268
3898
|
<div class="card card-wide">
|
|
3269
3899
|
<div class="card-hd">${t("CONFIG.FOLDER_SECTION")}</div>
|
|
3270
3900
|
<div class="fs-layout">
|
|
3271
|
-
<pre class="fs-tree">cypress/ <span style="color:#484f58"
|
|
3901
|
+
<pre class="fs-tree">cypress/ <span style="color:#484f58">${t("RECORDER.FS_TREE_PICK_HINT")}</span>
|
|
3272
3902
|
\u2514\u2500\u2500 e2e/
|
|
3273
3903
|
\u2514\u2500\u2500 *.cy.ts</pre>
|
|
3274
3904
|
<div class="fs-right">
|
|
3275
3905
|
<div class="fs-status">
|
|
3276
3906
|
<span class="fs-dot ${filesystemGranted ? "on" : "off"}"></span>
|
|
3277
|
-
${filesystemGranted && cypressFolderName ? `<span>${t("CONFIG.FOLDER_CONNECTED")}</span> <span class="fs-folder">\u{1F4C1} ${cypressFolderName}</span>` : `<span>${t("CONFIG.FOLDER_NOT_SET")}</span>`}
|
|
3907
|
+
${filesystemGranted && cypressFolderName ? `<span>${t("CONFIG.FOLDER_CONNECTED")}</span> <span class="fs-folder">\u{1F4C1} ${escHtml(cypressFolderName)}</span>` : `<span>${t("CONFIG.FOLDER_NOT_SET")}</span>`}
|
|
3278
3908
|
</div>
|
|
3279
3909
|
<div class="btn-row">
|
|
3280
3910
|
<button id="btn-change-folder">
|
|
@@ -3299,7 +3929,7 @@ function renderConfiguration(state, t) {
|
|
|
3299
3929
|
</div>
|
|
3300
3930
|
</div>
|
|
3301
3931
|
|
|
3302
|
-
</div
|
|
3932
|
+
</div>${renderExportOverlay(state, t)}`;
|
|
3303
3933
|
}
|
|
3304
3934
|
|
|
3305
3935
|
// src/components/configuration/configuration.ts
|
|
@@ -3311,6 +3941,13 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3311
3941
|
advancedHttpConfig = false;
|
|
3312
3942
|
selectorStrategy = "data-cy";
|
|
3313
3943
|
smartSelectorEnabled = true;
|
|
3944
|
+
startHidden = false;
|
|
3945
|
+
resumeTtlMinutes = DEFAULT_RESUME_TTL_MINUTES;
|
|
3946
|
+
isExporting = false;
|
|
3947
|
+
exportMode = "all";
|
|
3948
|
+
exportTests = [];
|
|
3949
|
+
exportSelectedIds = /* @__PURE__ */ new Set();
|
|
3950
|
+
exportSelectedTags = /* @__PURE__ */ new Set();
|
|
3314
3951
|
filesystemGranted = false;
|
|
3315
3952
|
cypressFolderName = null;
|
|
3316
3953
|
constructor() {
|
|
@@ -3336,6 +3973,12 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3336
3973
|
this.advancedHttpConfig = localStorage.getItem("extendedHttpCommands") === "true";
|
|
3337
3974
|
this.selectorStrategy = config?.["selectorStrategy"] ?? "data-cy";
|
|
3338
3975
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
3976
|
+
this.startHidden = config?.["startHidden"] === "true";
|
|
3977
|
+
const ttlRaw = config?.[RESUME_TTL_CONFIG_KEY];
|
|
3978
|
+
if (ttlRaw !== void 0 && ttlRaw !== null) {
|
|
3979
|
+
const ttl = Number(ttlRaw);
|
|
3980
|
+
this.resumeTtlMinutes = Number.isFinite(ttl) && ttl > 0 ? ttl : DEFAULT_RESUME_TTL_MINUTES;
|
|
3981
|
+
}
|
|
3339
3982
|
this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
|
|
3340
3983
|
const handle = config?.["cypressDirectoryHandle"];
|
|
3341
3984
|
this.cypressFolderName = handle?.name ?? null;
|
|
@@ -3353,6 +3996,18 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3353
3996
|
this.persistence.setConfig({ extendedHttpCommands: checked ? "true" : "false" });
|
|
3354
3997
|
this.render();
|
|
3355
3998
|
}
|
|
3999
|
+
async onStartHiddenChange(checked) {
|
|
4000
|
+
this.startHidden = checked;
|
|
4001
|
+
await this.persistence.setConfig({ startHidden: checked ? "true" : "false" });
|
|
4002
|
+
this.dispatchEvent(new CustomEvent("starthiddenchange", { detail: checked, bubbles: true, composed: true }));
|
|
4003
|
+
this.render();
|
|
4004
|
+
}
|
|
4005
|
+
async onResumeTtlChange(minutes) {
|
|
4006
|
+
const safe = Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes) : DEFAULT_RESUME_TTL_MINUTES;
|
|
4007
|
+
this.resumeTtlMinutes = safe;
|
|
4008
|
+
await this.persistence.setConfig({ [RESUME_TTL_CONFIG_KEY]: safe });
|
|
4009
|
+
this.render();
|
|
4010
|
+
}
|
|
3356
4011
|
async onSmartSelectorChange(enabled) {
|
|
3357
4012
|
this.smartSelectorEnabled = enabled;
|
|
3358
4013
|
await this.persistence.setConfig({ smartSelectorEnabled: enabled ? "true" : "false" });
|
|
@@ -3382,8 +4037,7 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3382
4037
|
this.cypressFolderName = null;
|
|
3383
4038
|
this.render();
|
|
3384
4039
|
}
|
|
3385
|
-
|
|
3386
|
-
const tests = await this.persistence.getAllTests();
|
|
4040
|
+
downloadTests(tests) {
|
|
3387
4041
|
const blob = new Blob([JSON.stringify({ tests, interceptors: [] }, null, 2)], { type: "application/json" });
|
|
3388
4042
|
const url = URL.createObjectURL(blob);
|
|
3389
4043
|
const a = document.createElement("a");
|
|
@@ -3392,6 +4046,53 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3392
4046
|
a.click();
|
|
3393
4047
|
URL.revokeObjectURL(url);
|
|
3394
4048
|
}
|
|
4049
|
+
/** Downloads every saved test (used by the "Todo" mode and as a direct API). */
|
|
4050
|
+
async exportAllData() {
|
|
4051
|
+
const tests = await this.persistence.getAllTests();
|
|
4052
|
+
this.downloadTests(tests);
|
|
4053
|
+
}
|
|
4054
|
+
/** Opens the export selection dialog, loading the current tests. */
|
|
4055
|
+
async openExportDialog() {
|
|
4056
|
+
this.exportTests = await this.persistence.getAllTests();
|
|
4057
|
+
this.exportMode = "all";
|
|
4058
|
+
this.exportSelectedIds = /* @__PURE__ */ new Set();
|
|
4059
|
+
this.exportSelectedTags = /* @__PURE__ */ new Set();
|
|
4060
|
+
this.isExporting = true;
|
|
4061
|
+
this.render();
|
|
4062
|
+
}
|
|
4063
|
+
cancelExport() {
|
|
4064
|
+
this.isExporting = false;
|
|
4065
|
+
this.exportSelectedIds.clear();
|
|
4066
|
+
this.exportSelectedTags.clear();
|
|
4067
|
+
this.render();
|
|
4068
|
+
}
|
|
4069
|
+
setExportMode(mode) {
|
|
4070
|
+
this.exportMode = mode;
|
|
4071
|
+
this.render();
|
|
4072
|
+
}
|
|
4073
|
+
toggleExportTest(id) {
|
|
4074
|
+
if (this.exportSelectedIds.has(id)) this.exportSelectedIds.delete(id);
|
|
4075
|
+
else this.exportSelectedIds.add(id);
|
|
4076
|
+
this.render();
|
|
4077
|
+
}
|
|
4078
|
+
toggleExportTag(tag) {
|
|
4079
|
+
if (this.exportSelectedTags.has(tag)) this.exportSelectedTags.delete(tag);
|
|
4080
|
+
else this.exportSelectedTags.add(tag);
|
|
4081
|
+
this.render();
|
|
4082
|
+
}
|
|
4083
|
+
/** Downloads the tests resolved by the current mode + selection. No-op if empty. */
|
|
4084
|
+
confirmExport() {
|
|
4085
|
+
const subset = selectTestsForExport(this.exportTests, this.exportMode, {
|
|
4086
|
+
ids: this.exportSelectedIds,
|
|
4087
|
+
tags: this.exportSelectedTags
|
|
4088
|
+
});
|
|
4089
|
+
if (!subset.length) return;
|
|
4090
|
+
this.downloadTests(subset);
|
|
4091
|
+
this.isExporting = false;
|
|
4092
|
+
this.exportSelectedIds.clear();
|
|
4093
|
+
this.exportSelectedTags.clear();
|
|
4094
|
+
this.render();
|
|
4095
|
+
}
|
|
3395
4096
|
async importAllData(file) {
|
|
3396
4097
|
const text = await file.text();
|
|
3397
4098
|
let data;
|
|
@@ -3403,7 +4104,6 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3403
4104
|
if (!data || !Array.isArray(data.tests) || !Array.isArray(data.interceptors)) {
|
|
3404
4105
|
throw new Error(this.t("CONFIG.JSON_BAD_FORMAT"));
|
|
3405
4106
|
}
|
|
3406
|
-
await this.persistence.clearAllData();
|
|
3407
4107
|
await this.persistence.ingestFileData(data.tests, data.interceptors);
|
|
3408
4108
|
}
|
|
3409
4109
|
render() {
|
|
@@ -3413,7 +4113,14 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3413
4113
|
selectorStrategy: this.selectorStrategy,
|
|
3414
4114
|
filesystemGranted: this.filesystemGranted,
|
|
3415
4115
|
cypressFolderName: this.cypressFolderName,
|
|
3416
|
-
smartSelectorEnabled: this.smartSelectorEnabled
|
|
4116
|
+
smartSelectorEnabled: this.smartSelectorEnabled,
|
|
4117
|
+
startHidden: this.startHidden,
|
|
4118
|
+
resumeTtlMinutes: this.resumeTtlMinutes,
|
|
4119
|
+
isExporting: this.isExporting,
|
|
4120
|
+
exportMode: this.exportMode,
|
|
4121
|
+
exportTests: this.exportTests,
|
|
4122
|
+
exportSelectedIds: this.exportSelectedIds,
|
|
4123
|
+
exportSelectedTags: this.exportSelectedTags
|
|
3417
4124
|
}, this.t.bind(this))}`;
|
|
3418
4125
|
;
|
|
3419
4126
|
this.shadow.getElementById("lang-select").addEventListener(
|
|
@@ -3428,13 +4135,32 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3428
4135
|
"change",
|
|
3429
4136
|
(e) => this.onSmartSelectorChange(e.target.checked)
|
|
3430
4137
|
);
|
|
4138
|
+
this.shadow.getElementById("start-hidden-toggle").addEventListener(
|
|
4139
|
+
"change",
|
|
4140
|
+
(e) => this.onStartHiddenChange(e.target.checked)
|
|
4141
|
+
);
|
|
4142
|
+
this.shadow.getElementById("resume-ttl-input").addEventListener(
|
|
4143
|
+
"change",
|
|
4144
|
+
(e) => this.onResumeTtlChange(Number(e.target.value))
|
|
4145
|
+
);
|
|
3431
4146
|
this.shadow.getElementById("selector-strategy").addEventListener(
|
|
3432
4147
|
"change",
|
|
3433
4148
|
(e) => this.onSelectorStrategyChange(e.target.value)
|
|
3434
4149
|
);
|
|
3435
4150
|
this.shadow.getElementById("btn-change-folder")?.addEventListener("click", () => this.changeFolder());
|
|
3436
4151
|
this.shadow.getElementById("btn-revoke")?.addEventListener("click", () => this.revokeAccess());
|
|
3437
|
-
this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.
|
|
4152
|
+
this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.openExportDialog());
|
|
4153
|
+
this.shadow.getElementById("btn-export-confirm")?.addEventListener("click", () => this.confirmExport());
|
|
4154
|
+
this.shadow.getElementById("btn-export-cancel")?.addEventListener("click", () => this.cancelExport());
|
|
4155
|
+
this.shadow.querySelectorAll("[data-export-mode]").forEach(
|
|
4156
|
+
(el) => el.addEventListener("click", () => this.setExportMode(el.dataset["exportMode"]))
|
|
4157
|
+
);
|
|
4158
|
+
this.shadow.querySelectorAll("[data-export-test]").forEach(
|
|
4159
|
+
(el) => el.addEventListener("change", () => this.toggleExportTest(Number(el.dataset["exportTest"])))
|
|
4160
|
+
);
|
|
4161
|
+
this.shadow.querySelectorAll("[data-export-tag]").forEach(
|
|
4162
|
+
(el) => el.addEventListener("click", () => this.toggleExportTag(el.dataset["exportTag"] ?? ""))
|
|
4163
|
+
);
|
|
3438
4164
|
this.shadow.getElementById("file-input").addEventListener("change", async (e) => {
|
|
3439
4165
|
const file = e.target.files?.[0];
|
|
3440
4166
|
if (!file) return;
|
|
@@ -3500,12 +4226,21 @@ var ADVANCED_TEST_EDITOR_STYLES = `
|
|
|
3500
4226
|
.file-name { font-size: 11px; color: #484f58; margin-bottom: 8px; font-family: monospace; }
|
|
3501
4227
|
pre {
|
|
3502
4228
|
background: #0d1117; padding: 12px; border-radius: 8px;
|
|
3503
|
-
font-size: 11px; color: #c9d1d9;
|
|
3504
|
-
white-space: pre-wrap; word-break: break-all; margin: 0;
|
|
4229
|
+
font-size: 11px; color: #c9d1d9; margin: 0;
|
|
3505
4230
|
font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
|
|
3506
|
-
line-height: 1.7;
|
|
3507
|
-
|
|
4231
|
+
line-height: 1.7; border: 1px solid #21262d;
|
|
4232
|
+
white-space: pre; overflow-x: auto; overflow-y: auto;
|
|
4233
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
3508
4234
|
}
|
|
4235
|
+
pre::-webkit-scrollbar { width: 4px; height: 4px; }
|
|
4236
|
+
pre::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
|
|
4237
|
+
.pre-block { font-size: 10.5px; }
|
|
4238
|
+
.pre-icp .sh-str { color: #85e89d; }
|
|
4239
|
+
.sh-kw { color: #ff7b72; }
|
|
4240
|
+
.sh-bi { color: #d2a8ff; }
|
|
4241
|
+
.sh-str { color: #a5d6ff; }
|
|
4242
|
+
.sh-cmt { color: #8b949e; font-style: italic; }
|
|
4243
|
+
.sh-num { color: #79c0ff; }
|
|
3509
4244
|
.footer {
|
|
3510
4245
|
padding: 10px 14px; border-top: 1px solid #21262d;
|
|
3511
4246
|
display: flex; gap: 8px; justify-content: flex-end; background: #161b22;
|
|
@@ -3521,6 +4256,12 @@ var ADVANCED_TEST_EDITOR_STYLES = `
|
|
|
3521
4256
|
.btn-save:hover { background: #1f6feb; border-color: #1f6feb; color: #fff; }
|
|
3522
4257
|
.btn-save:disabled { background: #21262d; border-color: #30363d; color: #8b949e; }
|
|
3523
4258
|
.placeholder { color: #484f58; font-size: 13px; padding: 28px; text-align: center; }
|
|
4259
|
+
.test-notes {
|
|
4260
|
+
margin: 0 0 10px; padding: 8px 12px;
|
|
4261
|
+
background: rgba(47,129,247,0.06); border-left: 3px solid rgba(47,129,247,0.4);
|
|
4262
|
+
border-radius: 0 6px 6px 0; font-size: 12px; color: #8b949e;
|
|
4263
|
+
line-height: 1.6; white-space: pre-wrap; word-break: break-word;
|
|
4264
|
+
}
|
|
3524
4265
|
.block-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
|
|
3525
4266
|
.btn-copy {
|
|
3526
4267
|
padding: 3px 10px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
|
|
@@ -3529,18 +4270,21 @@ var ADVANCED_TEST_EDITOR_STYLES = `
|
|
|
3529
4270
|
}
|
|
3530
4271
|
.btn-copy:hover { background: #30363d; color: #e6edf3; }
|
|
3531
4272
|
.sidebar-toolbar {
|
|
3532
|
-
display: flex; gap: 4px; padding: 6px 6px 4px;
|
|
4273
|
+
display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 6px 4px;
|
|
3533
4274
|
border-bottom: 1px solid #21262d; flex-shrink: 0; background: #0d1117;
|
|
3534
4275
|
}
|
|
3535
4276
|
.btn-toolbar {
|
|
3536
|
-
flex: 1; padding: 4px 6px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
|
|
4277
|
+
flex: 1 1 calc(50% - 2px); padding: 4px 6px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
|
|
3537
4278
|
font-size: 10px; font-weight: 500; background: #161b22; color: #8b949e;
|
|
3538
4279
|
transition: background 0.12s, color 0.12s; white-space: nowrap; overflow: hidden;
|
|
3539
4280
|
text-overflow: ellipsis;
|
|
3540
4281
|
}
|
|
4282
|
+
.btn-toolbar.btn-full { flex: 1 1 100%; }
|
|
3541
4283
|
.btn-toolbar:hover { background: #30363d; color: #e6edf3; }
|
|
3542
4284
|
.btn-toolbar.btn-new { color: #3fb950; border-color: #238636; }
|
|
3543
4285
|
.btn-toolbar.btn-new:hover { background: #238636; color: #fff; }
|
|
4286
|
+
.btn-toolbar.btn-new-folder { color: #d29922; border-color: #9e6a03; }
|
|
4287
|
+
.btn-toolbar.btn-new-folder:hover { background: #9e6a03; color: #fff; }
|
|
3544
4288
|
.new-file-form {
|
|
3545
4289
|
padding: 6px; border-bottom: 1px solid #21262d; display: flex; flex-direction: column; gap: 4px;
|
|
3546
4290
|
}
|
|
@@ -3574,23 +4318,24 @@ function renderNoPermission(needsReauth, t) {
|
|
|
3574
4318
|
</div>`;
|
|
3575
4319
|
}
|
|
3576
4320
|
function renderAdvancedEditor(state, t) {
|
|
3577
|
-
const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, saveButtonEnabled, isCreatingFile, collapsedDirs, sidebarWidth } = state;
|
|
4321
|
+
const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, testNotes, saveButtonEnabled, isCreatingFile, isCreatingFolder, collapsedDirs, sidebarWidth } = state;
|
|
3578
4322
|
const treeHtml = e2eTree.length ? renderTree(e2eTree, selectedFile, collapsedDirs) : `<div class="tree-item" style="color:#6c7a99">${t("ADVANCED_EDITOR.NO_FILES")}</div>`;
|
|
3579
4323
|
const contentHtml = selectedFileContent ? `<div class="file-name">\u{1F4C4} ${escHtml(selectedFile?.name ?? "")}</div>
|
|
3580
|
-
<pre>${
|
|
4324
|
+
<pre class="pre-file">${syntaxHighlight(selectedFileContent.slice(0, 6e3))}${selectedFileContent.length > 6e3 ? "\n..." : ""}</pre>` : `<div class="placeholder">${t("ADVANCED_EDITOR.SELECT_FILE")}</div>`;
|
|
4325
|
+
const notesHtml = testItBlock && testNotes ? `<p class="test-notes">${escHtml(testNotes)}</p>` : "";
|
|
3581
4326
|
const itHtml = testItBlock ? `<div style="margin-top:10px">
|
|
3582
4327
|
<div class="file-name block-header">
|
|
3583
4328
|
<span>${t("ADVANCED_EDITOR.IT_LABEL")}</span>
|
|
3584
4329
|
<button id="btn-copy-it" class="btn-copy">${t("ADVANCED_EDITOR.COPY_IT_BTN")}</button>
|
|
3585
4330
|
</div>
|
|
3586
|
-
<pre
|
|
4331
|
+
<pre class="pre-block">${syntaxHighlight(testItBlock.slice(0, 3e3))}</pre>
|
|
3587
4332
|
</div>` : "";
|
|
3588
4333
|
const interceptorsHtml = interceptorsBlock ? `<div style="margin-top:10px">
|
|
3589
4334
|
<div class="file-name block-header">
|
|
3590
4335
|
<span>${t("ADVANCED_EDITOR.BEFORE_EACH_LABEL")}</span>
|
|
3591
4336
|
<button id="btn-copy-interceptors" class="btn-copy">${t("ADVANCED_EDITOR.COPY_ICP_BTN")}</button>
|
|
3592
4337
|
</div>
|
|
3593
|
-
<pre
|
|
4338
|
+
<pre class="pre-block pre-icp">${syntaxHighlight(interceptorsBlock.slice(0, 3e3))}</pre>
|
|
3594
4339
|
</div>` : "";
|
|
3595
4340
|
const newFileForm = isCreatingFile ? `<div class="new-file-form">
|
|
3596
4341
|
<input id="input-new-file" type="text" placeholder="${escHtml(t("ADVANCED_EDITOR.NEW_FILE_PLACEHOLDER"))}" autocomplete="off" />
|
|
@@ -3600,21 +4345,30 @@ function renderAdvancedEditor(state, t) {
|
|
|
3600
4345
|
<button id="btn-new-file-cancel" class="btn-cancel-form">${t("ADVANCED_EDITOR.NEW_FILE_CANCEL")}</button>
|
|
3601
4346
|
</div>
|
|
3602
4347
|
</div>` : "";
|
|
4348
|
+
const newFolderForm = isCreatingFolder ? `<div class="new-file-form">
|
|
4349
|
+
<input id="input-new-folder" type="text" placeholder="${escHtml(t("ADVANCED_EDITOR.NEW_FOLDER_PLACEHOLDER"))}" autocomplete="off" />
|
|
4350
|
+
<div class="new-file-actions">
|
|
4351
|
+
<button id="btn-new-folder-confirm" class="btn-confirm">${t("ADVANCED_EDITOR.NEW_FILE_CONFIRM")}</button>
|
|
4352
|
+
<button id="btn-new-folder-cancel" class="btn-cancel-form">${t("ADVANCED_EDITOR.NEW_FILE_CANCEL")}</button>
|
|
4353
|
+
</div>
|
|
4354
|
+
</div>` : "";
|
|
3603
4355
|
const toolbar = `
|
|
3604
4356
|
<div class="sidebar-toolbar">
|
|
3605
4357
|
<button id="btn-new-file" class="btn-toolbar btn-new">${t("ADVANCED_EDITOR.NEW_FILE_BTN")}</button>
|
|
3606
|
-
<button id="btn-
|
|
4358
|
+
<button id="btn-new-folder" class="btn-toolbar btn-new-folder">${t("ADVANCED_EDITOR.NEW_FOLDER_BTN")}</button>
|
|
4359
|
+
<button id="btn-refresh" class="btn-toolbar btn-full">${t("ADVANCED_EDITOR.REFRESH_BTN")}</button>
|
|
3607
4360
|
</div>`;
|
|
3608
4361
|
return `
|
|
3609
4362
|
<div class="layout">
|
|
3610
4363
|
<div class="sidebar" style="width:${sidebarWidth}px">
|
|
3611
4364
|
${toolbar}
|
|
3612
4365
|
${newFileForm}
|
|
4366
|
+
${newFolderForm}
|
|
3613
4367
|
<div class="tree-scroll">${treeHtml}</div>
|
|
3614
4368
|
</div>
|
|
3615
4369
|
<div id="resize-handle" class="resize-handle"></div>
|
|
3616
4370
|
<div class="main">
|
|
3617
|
-
<div class="content-area">${contentHtml}${itHtml}${interceptorsHtml}</div>
|
|
4371
|
+
<div class="content-area">${contentHtml}${notesHtml}${itHtml}${interceptorsHtml}</div>
|
|
3618
4372
|
<div class="footer">
|
|
3619
4373
|
<button id="btn-save" class="btn-save"
|
|
3620
4374
|
${!saveButtonEnabled || !testItBlock ? "disabled" : ""}>
|
|
@@ -3675,10 +4429,12 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3675
4429
|
selectedFileContent = null;
|
|
3676
4430
|
testItBlock = "";
|
|
3677
4431
|
interceptorsBlock = "";
|
|
4432
|
+
testNotes = "";
|
|
3678
4433
|
isPreviewMode = false;
|
|
3679
4434
|
previewFileName = null;
|
|
3680
4435
|
previewFileContent = null;
|
|
3681
4436
|
isCreatingFile = false;
|
|
4437
|
+
isCreatingFolder = false;
|
|
3682
4438
|
collapsedDirs = /* @__PURE__ */ new Set();
|
|
3683
4439
|
sidebarWidth = 220;
|
|
3684
4440
|
hasPermission = false;
|
|
@@ -3741,7 +4497,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3741
4497
|
const name = rawName.trim().replace(/\.cy\.ts$/, "");
|
|
3742
4498
|
if (!name) return;
|
|
3743
4499
|
const fileName = `${name}.cy.ts`;
|
|
3744
|
-
const template = `describe('${name}', () => {
|
|
4500
|
+
const template = `describe('${escapeSingleQuotes(name)}', () => {
|
|
3745
4501
|
|
|
3746
4502
|
it('should ', () => {
|
|
3747
4503
|
|
|
@@ -3759,6 +4515,17 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3759
4515
|
this.isCreatingFile = false;
|
|
3760
4516
|
await this.getFoldersData();
|
|
3761
4517
|
}
|
|
4518
|
+
async createNewFolder(rawName) {
|
|
4519
|
+
if (!this._e2eHandle) return;
|
|
4520
|
+
const name = rawName.trim().replace(/[/\\]/g, "");
|
|
4521
|
+
if (!name) return;
|
|
4522
|
+
try {
|
|
4523
|
+
await this._e2eHandle.getDirectoryHandle(name, { create: true });
|
|
4524
|
+
} catch {
|
|
4525
|
+
}
|
|
4526
|
+
this.isCreatingFolder = false;
|
|
4527
|
+
await this.getFoldersData();
|
|
4528
|
+
}
|
|
3762
4529
|
async refreshTree() {
|
|
3763
4530
|
await this.getFoldersData();
|
|
3764
4531
|
}
|
|
@@ -3768,7 +4535,8 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3768
4535
|
if (this.interceptorsBlock) {
|
|
3769
4536
|
content = this.transformationService.insertBeforeEach(content, this.interceptorsBlock);
|
|
3770
4537
|
}
|
|
3771
|
-
|
|
4538
|
+
const comment = this.testNotes ? this.transformationService.buildBlockComment(this.testNotes) + "\n" : "";
|
|
4539
|
+
content = this.transformationService.insertItBlock(content, comment + this.testItBlock);
|
|
3772
4540
|
if (!content) return;
|
|
3773
4541
|
const writable = await this.selectedFileHandle.createWritable();
|
|
3774
4542
|
await writable.write(content);
|
|
@@ -3795,9 +4563,11 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3795
4563
|
if (test) {
|
|
3796
4564
|
this.testItBlock = test.itBlock ?? "";
|
|
3797
4565
|
this.interceptorsBlock = test.interceptorsBlock ?? "";
|
|
4566
|
+
this.testNotes = test.notes ?? "";
|
|
3798
4567
|
} else {
|
|
3799
4568
|
this.testItBlock = "";
|
|
3800
4569
|
this.interceptorsBlock = "";
|
|
4570
|
+
this.testNotes = "";
|
|
3801
4571
|
}
|
|
3802
4572
|
this.render();
|
|
3803
4573
|
}
|
|
@@ -3813,7 +4583,8 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3813
4583
|
fileName: this.selectedFile?.name ?? "",
|
|
3814
4584
|
testId: this.testId,
|
|
3815
4585
|
itBlock: this.testItBlock,
|
|
3816
|
-
interceptorsBlock: this.interceptorsBlock
|
|
4586
|
+
interceptorsBlock: this.interceptorsBlock,
|
|
4587
|
+
notes: this.testNotes
|
|
3817
4588
|
},
|
|
3818
4589
|
bubbles: true,
|
|
3819
4590
|
composed: true
|
|
@@ -3887,8 +4658,10 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3887
4658
|
selectedFileContent: this.selectedFileContent,
|
|
3888
4659
|
testItBlock: this.testItBlock,
|
|
3889
4660
|
interceptorsBlock: this.interceptorsBlock,
|
|
4661
|
+
testNotes: this.testNotes,
|
|
3890
4662
|
saveButtonEnabled: this.saveButtonEnabled,
|
|
3891
4663
|
isCreatingFile: this.isCreatingFile,
|
|
4664
|
+
isCreatingFolder: this.isCreatingFolder,
|
|
3892
4665
|
collapsedDirs: this.collapsedDirs,
|
|
3893
4666
|
sidebarWidth: this.sidebarWidth
|
|
3894
4667
|
}, this.t.bind(this))}`;
|
|
@@ -3912,9 +4685,16 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3912
4685
|
this.shadow.getElementById("btn-close")?.addEventListener("click", () => this.closePreview());
|
|
3913
4686
|
this.shadow.getElementById("btn-new-file")?.addEventListener("click", () => {
|
|
3914
4687
|
this.isCreatingFile = !this.isCreatingFile;
|
|
4688
|
+
this.isCreatingFolder = false;
|
|
3915
4689
|
this.render();
|
|
3916
4690
|
if (this.isCreatingFile) this.shadow.getElementById("input-new-file")?.focus();
|
|
3917
4691
|
});
|
|
4692
|
+
this.shadow.getElementById("btn-new-folder")?.addEventListener("click", () => {
|
|
4693
|
+
this.isCreatingFolder = !this.isCreatingFolder;
|
|
4694
|
+
this.isCreatingFile = false;
|
|
4695
|
+
this.render();
|
|
4696
|
+
if (this.isCreatingFolder) this.shadow.getElementById("input-new-folder")?.focus();
|
|
4697
|
+
});
|
|
3918
4698
|
this.shadow.getElementById("btn-refresh")?.addEventListener("click", () => this.refreshTree());
|
|
3919
4699
|
this.shadow.getElementById("btn-new-file-confirm")?.addEventListener("click", () => {
|
|
3920
4700
|
const input2 = this.shadow.getElementById("input-new-file");
|
|
@@ -3933,6 +4713,23 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3933
4713
|
this.render();
|
|
3934
4714
|
}
|
|
3935
4715
|
});
|
|
4716
|
+
this.shadow.getElementById("btn-new-folder-confirm")?.addEventListener("click", () => {
|
|
4717
|
+
const folderInput2 = this.shadow.getElementById("input-new-folder");
|
|
4718
|
+
this.createNewFolder(folderInput2?.value ?? "");
|
|
4719
|
+
});
|
|
4720
|
+
this.shadow.getElementById("btn-new-folder-cancel")?.addEventListener("click", () => {
|
|
4721
|
+
this.isCreatingFolder = false;
|
|
4722
|
+
this.render();
|
|
4723
|
+
});
|
|
4724
|
+
const folderInput = this.shadow.getElementById("input-new-folder");
|
|
4725
|
+
folderInput?.addEventListener("keydown", (e) => {
|
|
4726
|
+
if (e.key === "Enter") {
|
|
4727
|
+
this.createNewFolder(folderInput.value);
|
|
4728
|
+
} else if (e.key === "Escape") {
|
|
4729
|
+
this.isCreatingFolder = false;
|
|
4730
|
+
this.render();
|
|
4731
|
+
}
|
|
4732
|
+
});
|
|
3936
4733
|
}
|
|
3937
4734
|
};
|
|
3938
4735
|
if (!customElements.get("advanced-test-editor")) {
|
|
@@ -4018,7 +4815,30 @@ var FILE_PREVIEW_STYLES = `
|
|
|
4018
4815
|
.btn-save:hover { background: #2ea043; border-color: #2ea043; }
|
|
4019
4816
|
.btn-launch { background: transparent; border-color: #e3b341; color: #e3b341; }
|
|
4020
4817
|
.btn-launch:hover { background: rgba(227,179,65,0.1); }
|
|
4818
|
+
.btn-launch:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
4819
|
+
.btn-launch:disabled:hover { background: transparent; }
|
|
4820
|
+
.launch-hint { font-size: 10px; color: #8b6d3b; align-self: center; }
|
|
4821
|
+
.btn-insert { background: transparent; border-color: #a371f7; color: #a371f7; }
|
|
4822
|
+
.btn-insert:hover { background: rgba(163,113,247,0.12); color: #c8a8ff; }
|
|
4021
4823
|
.btn-diff-active { background: rgba(47,129,247,0.15); border-color: rgba(47,129,247,0.4); color: #2f81f7; }
|
|
4824
|
+
|
|
4825
|
+
/* \u2500\u2500 Run result panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
4826
|
+
.run-result {
|
|
4827
|
+
border-top: 1px solid #21262d; padding: 8px 12px; background: #0d1117;
|
|
4828
|
+
max-height: 220px; overflow-y: auto; flex-shrink: 0;
|
|
4829
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
4830
|
+
}
|
|
4831
|
+
.run-status { font-size: 12px; font-weight: 600; }
|
|
4832
|
+
.run-passed .run-status { color: #3fb950; }
|
|
4833
|
+
.run-failed .run-status { color: #f85149; }
|
|
4834
|
+
.run-running .run-status { color: #e3b341; }
|
|
4835
|
+
.run-error .run-status { color: #f85149; }
|
|
4836
|
+
.run-output {
|
|
4837
|
+
margin: 6px 0 0; padding: 8px 10px; background: #161b22; border: 1px solid #21262d;
|
|
4838
|
+
border-radius: 5px; font-size: 10px; color: #c9d1d9; line-height: 1.5;
|
|
4839
|
+
font-family: 'Cascadia Code','Fira Code','Consolas',monospace;
|
|
4840
|
+
white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow-y: auto;
|
|
4841
|
+
}
|
|
4022
4842
|
`;
|
|
4023
4843
|
|
|
4024
4844
|
// src/components/file-preview/file-preview.template.ts
|
|
@@ -4076,8 +4896,14 @@ function buildDiffHtml(original, current, t) {
|
|
|
4076
4896
|
return `<div class="diff-line ${type}"><span class="diff-sign">${sign}</span><span>${escHtml(line)}</span></div>`;
|
|
4077
4897
|
}).join("");
|
|
4078
4898
|
}
|
|
4899
|
+
var RUN_STATUS_KEY = {
|
|
4900
|
+
running: "FILE_PREVIEW.LAUNCH_RUNNING",
|
|
4901
|
+
passed: "FILE_PREVIEW.LAUNCH_PASSED",
|
|
4902
|
+
failed: "FILE_PREVIEW.LAUNCH_FAILED",
|
|
4903
|
+
error: "FILE_PREVIEW.LAUNCH_NO_RUNNER"
|
|
4904
|
+
};
|
|
4079
4905
|
function renderFilePreview(state, t) {
|
|
4080
|
-
const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel } = state;
|
|
4906
|
+
const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel, isLocal, runState, runOutput } = state;
|
|
4081
4907
|
const blocksPanelHtml = itBlock || interceptorsBlock ? `
|
|
4082
4908
|
<div class="blocks-panel">
|
|
4083
4909
|
${itBlock ? `
|
|
@@ -4098,6 +4924,13 @@ function renderFilePreview(state, t) {
|
|
|
4098
4924
|
</div>` : ""}
|
|
4099
4925
|
</div>` : "";
|
|
4100
4926
|
const hasChanges = originalContent !== null && originalContent !== (fileContent ?? "");
|
|
4927
|
+
const hasBlocks = !!(itBlock || interceptorsBlock);
|
|
4928
|
+
const launchBtnHtml = isLocal ? `<button id="btn-launch" class="btn-launch" ${runState === "running" ? "disabled" : ""}>${runState === "running" ? t("FILE_PREVIEW.LAUNCH_RUNNING") : t("FILE_PREVIEW.LAUNCH_BTN")}</button>` : `<button class="btn-launch" disabled title="${t("FILE_PREVIEW.LAUNCH_LOCAL_ONLY")}">${t("FILE_PREVIEW.LAUNCH_BTN")}</button>
|
|
4929
|
+
<span class="launch-hint">${t("FILE_PREVIEW.LAUNCH_LOCAL_ONLY")}</span>`;
|
|
4930
|
+
const runResultHtml = runState !== "idle" ? `<div class="run-result run-${runState}">
|
|
4931
|
+
<span class="run-status">${t(RUN_STATUS_KEY[runState])}</span>
|
|
4932
|
+
${runOutput ? `<pre class="run-output">${escHtml(runOutput.slice(0, 8e3))}</pre>` : ""}
|
|
4933
|
+
</div>` : "";
|
|
4101
4934
|
return `
|
|
4102
4935
|
<div class="container">
|
|
4103
4936
|
<div class="header">
|
|
@@ -4108,9 +4941,11 @@ function renderFilePreview(state, t) {
|
|
|
4108
4941
|
${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea>`}
|
|
4109
4942
|
${blocksPanelHtml}
|
|
4110
4943
|
</div>
|
|
4944
|
+
${runResultHtml}
|
|
4111
4945
|
<div class="footer">
|
|
4112
|
-
|
|
4946
|
+
${launchBtnHtml}
|
|
4113
4947
|
${hasChanges ? `<button id="btn-diff" class="${showDiff ? "btn-diff-active" : ""}">${t("FILE_PREVIEW.DIFF_BTN")}</button>` : ""}
|
|
4948
|
+
${hasBlocks && !showDiff ? `<button id="btn-insert" class="btn-insert" title="${t("FILE_PREVIEW.INSERT_TITLE")}">${t("FILE_PREVIEW.INSERT_BTN")}</button>` : ""}
|
|
4114
4949
|
<button id="btn-save" class="btn-save">${t("FILE_PREVIEW.SAVE_BTN")}</button>
|
|
4115
4950
|
<button id="btn-close">${escHtml(closeLabel || t("FILE_PREVIEW.CLOSE_BTN"))}</button>
|
|
4116
4951
|
</div>
|
|
@@ -4126,11 +4961,18 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4126
4961
|
translation = translationService;
|
|
4127
4962
|
itBlock = "";
|
|
4128
4963
|
interceptorsBlock = "";
|
|
4964
|
+
notes = "";
|
|
4129
4965
|
commands = [];
|
|
4130
4966
|
interceptors = [];
|
|
4967
|
+
/** Endpoint of the local Cypress runner. Configurable; defaults to the bundled runner's port. */
|
|
4968
|
+
runnerUrl = "http://localhost:8123/run-test";
|
|
4969
|
+
/** Whether the app is served locally — gates the launch button. Overridable for tests. */
|
|
4970
|
+
isLocal = isLocalHost(window.location.hostname);
|
|
4131
4971
|
_fileContent = null;
|
|
4132
4972
|
_originalContent = null;
|
|
4133
4973
|
_showDiff = false;
|
|
4974
|
+
_runState = "idle";
|
|
4975
|
+
_runOutput = "";
|
|
4134
4976
|
constructor() {
|
|
4135
4977
|
super();
|
|
4136
4978
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -4158,15 +5000,34 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4158
5000
|
onClose() {
|
|
4159
5001
|
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
4160
5002
|
}
|
|
5003
|
+
/**
|
|
5004
|
+
* Runs the current spec headless via the local runner and shows the result.
|
|
5005
|
+
* No-op off localhost. Never throws — connection failures surface as an
|
|
5006
|
+
* "error" state + toast instead of an unhandled rejection.
|
|
5007
|
+
*/
|
|
4161
5008
|
async launchTest(specPath) {
|
|
4162
|
-
|
|
5009
|
+
if (!this.isLocal) return;
|
|
5010
|
+
const path = specPath ?? this.fileName ?? "";
|
|
4163
5011
|
if (!path) return;
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
5012
|
+
this._runState = "running";
|
|
5013
|
+
this._runOutput = "";
|
|
5014
|
+
this.render();
|
|
5015
|
+
try {
|
|
5016
|
+
const response = await fetch(this.runnerUrl, {
|
|
5017
|
+
method: "POST",
|
|
5018
|
+
headers: { "Content-Type": "application/json" },
|
|
5019
|
+
body: JSON.stringify({ specPath: path })
|
|
5020
|
+
});
|
|
5021
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
5022
|
+
const result = await response.json();
|
|
5023
|
+
this._runOutput = result.output ?? "";
|
|
5024
|
+
this._runState = result.success ? "passed" : "failed";
|
|
5025
|
+
} catch {
|
|
5026
|
+
this._runState = "error";
|
|
5027
|
+
this._runOutput = "";
|
|
5028
|
+
showToast(this.t("FILE_PREVIEW.LAUNCH_NO_RUNNER"), false);
|
|
5029
|
+
}
|
|
5030
|
+
this.render();
|
|
4170
5031
|
}
|
|
4171
5032
|
copyToClipboard(text) {
|
|
4172
5033
|
navigator.clipboard?.writeText(text);
|
|
@@ -4175,6 +5036,32 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4175
5036
|
this._showDiff = !this._showDiff;
|
|
4176
5037
|
this.render();
|
|
4177
5038
|
}
|
|
5039
|
+
/**
|
|
5040
|
+
* Merges the it() and beforeEach() blocks into the current editor content
|
|
5041
|
+
* using the same logic as the automatic "Insert into file" action, so the
|
|
5042
|
+
* user does not have to copy/paste manually. Leaves the content untouched
|
|
5043
|
+
* (and warns) when the file has no valid describe() block to insert into.
|
|
5044
|
+
*/
|
|
5045
|
+
insertBlocks() {
|
|
5046
|
+
const base = this.textarea?.value ?? this._fileContent ?? "";
|
|
5047
|
+
let content = base;
|
|
5048
|
+
if (this.interceptorsBlock) {
|
|
5049
|
+
const merged = advancedTestTransformationService.insertBeforeEach(content, this.interceptorsBlock);
|
|
5050
|
+
if (merged) content = merged;
|
|
5051
|
+
}
|
|
5052
|
+
if (this.itBlock) {
|
|
5053
|
+
const comment = this.notes ? advancedTestTransformationService.buildBlockComment(this.notes) + "\n" : "";
|
|
5054
|
+
const merged = advancedTestTransformationService.insertItBlock(content, comment + this.itBlock);
|
|
5055
|
+
if (merged) content = merged;
|
|
5056
|
+
}
|
|
5057
|
+
if (content === base) {
|
|
5058
|
+
showToast(this.t("FILE_PREVIEW.INSERT_ERROR"), false);
|
|
5059
|
+
return;
|
|
5060
|
+
}
|
|
5061
|
+
this._fileContent = content;
|
|
5062
|
+
this.render();
|
|
5063
|
+
showToast(this.t("FILE_PREVIEW.INSERT_DONE"));
|
|
5064
|
+
}
|
|
4178
5065
|
render() {
|
|
4179
5066
|
this.shadow.innerHTML = `<style>${FILE_PREVIEW_STYLES}</style>${renderFilePreview({
|
|
4180
5067
|
fileName: this.fileName,
|
|
@@ -4184,7 +5071,10 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4184
5071
|
currentContent: this.textarea?.value ?? this._fileContent ?? "",
|
|
4185
5072
|
itBlock: this.itBlock,
|
|
4186
5073
|
interceptorsBlock: this.interceptorsBlock,
|
|
4187
|
-
closeLabel: this.closeLabel
|
|
5074
|
+
closeLabel: this.closeLabel,
|
|
5075
|
+
isLocal: this.isLocal,
|
|
5076
|
+
runState: this._runState,
|
|
5077
|
+
runOutput: this._runOutput
|
|
4188
5078
|
}, this.t.bind(this))}`;
|
|
4189
5079
|
this.textarea = this.shadow.getElementById("editor");
|
|
4190
5080
|
if (this.textarea) {
|
|
@@ -4200,6 +5090,7 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4200
5090
|
this.copyToClipboard(this.textarea?.value ?? this._fileContent ?? "");
|
|
4201
5091
|
});
|
|
4202
5092
|
this.shadow.getElementById("btn-diff")?.addEventListener("click", () => this.toggleDiff());
|
|
5093
|
+
this.shadow.getElementById("btn-insert")?.addEventListener("click", () => this.insertBlocks());
|
|
4203
5094
|
this.shadow.getElementById("btn-copy-it")?.addEventListener("click", () => {
|
|
4204
5095
|
this.copyToClipboard(this.itBlock);
|
|
4205
5096
|
});
|
|
@@ -4447,6 +5338,9 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4447
5338
|
interceptorsUnsub;
|
|
4448
5339
|
pauseUnsub;
|
|
4449
5340
|
selectorNotFoundUnsub;
|
|
5341
|
+
langUnsub;
|
|
5342
|
+
sessionUnsub;
|
|
5343
|
+
sessionSaveTimer;
|
|
4450
5344
|
controlFirstTimeData = true;
|
|
4451
5345
|
_previsualizerRef = null;
|
|
4452
5346
|
httpMonitor;
|
|
@@ -4454,6 +5348,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4454
5348
|
recording;
|
|
4455
5349
|
persistence;
|
|
4456
5350
|
translation;
|
|
5351
|
+
isVisible = false;
|
|
4457
5352
|
isRecording = false;
|
|
4458
5353
|
isPaused = false;
|
|
4459
5354
|
cypressCommands = [];
|
|
@@ -4488,17 +5383,22 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4488
5383
|
this.initSelectorStrategy();
|
|
4489
5384
|
this.initSubscriptions();
|
|
4490
5385
|
this.render();
|
|
5386
|
+
this.initVisibility();
|
|
5387
|
+
this.initSessionContinuity();
|
|
4491
5388
|
this.keydownHandler = (e) => this.handleKeyboardEvent(e);
|
|
4492
5389
|
window.addEventListener("keydown", this.keydownHandler);
|
|
4493
5390
|
}
|
|
4494
5391
|
disconnectedCallback() {
|
|
4495
5392
|
if (this._isDisabled) return;
|
|
4496
5393
|
window.removeEventListener("keydown", this.keydownHandler);
|
|
5394
|
+
this.flushActiveSessionOnDisconnect();
|
|
4497
5395
|
this.recordingUnsub?.();
|
|
4498
5396
|
this.commandsUnsub?.();
|
|
4499
5397
|
this.interceptorsUnsub?.();
|
|
4500
5398
|
this.pauseUnsub?.();
|
|
4501
5399
|
this.selectorNotFoundUnsub?.();
|
|
5400
|
+
this.langUnsub?.();
|
|
5401
|
+
this.sessionUnsub?.();
|
|
4502
5402
|
this.httpMonitor?.uninstall();
|
|
4503
5403
|
this.recording.destroy();
|
|
4504
5404
|
}
|
|
@@ -4521,11 +5421,13 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4521
5421
|
this.isRecording = val;
|
|
4522
5422
|
if (!val && !this.controlFirstTimeData) {
|
|
4523
5423
|
this.saveRecordingHistory();
|
|
5424
|
+
this.clearSessionPersistence();
|
|
4524
5425
|
this.showSaveTestDialog();
|
|
4525
5426
|
}
|
|
4526
5427
|
this.controlFirstTimeData = false;
|
|
4527
5428
|
this.render();
|
|
4528
5429
|
});
|
|
5430
|
+
this.sessionUnsub = this.recording.onSessionChange((state) => this.persistActiveSession(state));
|
|
4529
5431
|
this.commandsUnsub = this.recording.onCommandsChange((cmds) => {
|
|
4530
5432
|
this.cypressCommands = cmds;
|
|
4531
5433
|
if (this._previsualizerRef) this._previsualizerRef.commands = cmds;
|
|
@@ -4541,6 +5443,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4541
5443
|
this.selectorNotFoundUnsub = this.recording.onSelectorNotFound((target) => {
|
|
4542
5444
|
if (this.smartSelectorEnabled) this.showSelectorPicker(target);
|
|
4543
5445
|
});
|
|
5446
|
+
this.langUnsub = this.translation.onLangChange(() => this.render());
|
|
4544
5447
|
}
|
|
4545
5448
|
showSelectorPicker(target) {
|
|
4546
5449
|
Promise.resolve().then(() => (init_selector_picker(), selector_picker_exports)).then(() => {
|
|
@@ -4553,12 +5456,135 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4553
5456
|
document.body.appendChild(picker);
|
|
4554
5457
|
});
|
|
4555
5458
|
}
|
|
5459
|
+
async initVisibility() {
|
|
5460
|
+
if (this.hasAttribute("start-hidden")) {
|
|
5461
|
+
this.isVisible = this.getAttribute("start-hidden") === "false";
|
|
5462
|
+
this.style.display = this.isVisible ? "" : "none";
|
|
5463
|
+
return;
|
|
5464
|
+
}
|
|
5465
|
+
const config = await this.persistence.getGeneralConfig();
|
|
5466
|
+
this.isVisible = config?.["startHidden"] !== "true";
|
|
5467
|
+
this.style.display = this.isVisible ? "" : "none";
|
|
5468
|
+
}
|
|
4556
5469
|
async initSelectorStrategy() {
|
|
4557
5470
|
const config = await this.persistence.getGeneralConfig();
|
|
4558
5471
|
const strategy = config?.["selectorStrategy"];
|
|
4559
5472
|
if (strategy) this.recording.selectorStrategy = strategy;
|
|
4560
5473
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
4561
5474
|
}
|
|
5475
|
+
// ── cross-app session continuity (spec 006) ───────────────────────────────
|
|
5476
|
+
/**
|
|
5477
|
+
* On mount, detect a persisted live recording session and either resume it
|
|
5478
|
+
* silently (recent) or prompt continue/discard (stale). No-op when there is
|
|
5479
|
+
* no actively-recording session.
|
|
5480
|
+
*/
|
|
5481
|
+
async initSessionContinuity() {
|
|
5482
|
+
if (this._isDisabled) return;
|
|
5483
|
+
const session = await this.persistence.getActiveSession();
|
|
5484
|
+
if (!session || !session.isRecording) return;
|
|
5485
|
+
const ttlMin = await this.getResumeTtlMinutes();
|
|
5486
|
+
const ageMs = Date.now() - session.updatedAt;
|
|
5487
|
+
if (ageMs <= ttlMin * 6e4) {
|
|
5488
|
+
this.resumeSessionState(session);
|
|
5489
|
+
} else {
|
|
5490
|
+
this.promptResumeOrDiscard(session);
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
async getResumeTtlMinutes() {
|
|
5494
|
+
const config = await this.persistence.getConfig(RESUME_TTL_CONFIG_KEY);
|
|
5495
|
+
const raw = config?.[RESUME_TTL_CONFIG_KEY];
|
|
5496
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
5497
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_RESUME_TTL_MINUTES;
|
|
5498
|
+
}
|
|
5499
|
+
/** Rehydrates the recorder from a persisted session (no bootstrap re-emitted). */
|
|
5500
|
+
resumeSessionState(session) {
|
|
5501
|
+
this.recording.restoreSession(session);
|
|
5502
|
+
this.cypressCommands = session.commands;
|
|
5503
|
+
this.interceptors = session.interceptors;
|
|
5504
|
+
this.controlFirstTimeData = false;
|
|
5505
|
+
this.render();
|
|
5506
|
+
}
|
|
5507
|
+
async promptResumeOrDiscard(session) {
|
|
5508
|
+
const t = this.translation.translate.bind(this.translation);
|
|
5509
|
+
const result = await import_sweetalert2.default.fire({
|
|
5510
|
+
title: t("RECORDER.SESSION_RESUME_TITLE"),
|
|
5511
|
+
html: `<div style="padding:8px 4px;color:#8b949e;font-size:13px;line-height:1.6"><p>${t("RECORDER.SESSION_RESUME_TEXT")}</p><p style="margin-top:6px;color:#c9d1d9"><b>${session.commands.length}</b> ${t("RECORDER.SESSION_RESUME_COUNT")}</p></div>`,
|
|
5512
|
+
showCancelButton: true,
|
|
5513
|
+
confirmButtonText: t("RECORDER.SESSION_CONTINUE_BTN"),
|
|
5514
|
+
cancelButtonText: t("RECORDER.SESSION_DISCARD_BTN"),
|
|
5515
|
+
color: "#e6edf3"
|
|
5516
|
+
});
|
|
5517
|
+
if (result && result.isConfirmed) {
|
|
5518
|
+
this.resumeSessionState(session);
|
|
5519
|
+
} else {
|
|
5520
|
+
this.discardSession();
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
/** Debounced persistence of the live session; clears it the moment recording stops. */
|
|
5524
|
+
persistActiveSession(state) {
|
|
5525
|
+
if (this._isDisabled) return;
|
|
5526
|
+
if (!state.isRecording) {
|
|
5527
|
+
this.clearSessionPersistence();
|
|
5528
|
+
return;
|
|
5529
|
+
}
|
|
5530
|
+
this.writeSessionBreadcrumb(state);
|
|
5531
|
+
if (this.sessionSaveTimer) clearTimeout(this.sessionSaveTimer);
|
|
5532
|
+
this.sessionSaveTimer = setTimeout(() => {
|
|
5533
|
+
const snap = this.recording.getSessionSnapshot();
|
|
5534
|
+
if (snap.isRecording) this.persistence.saveActiveSession(snap).catch(() => {
|
|
5535
|
+
});
|
|
5536
|
+
}, 300);
|
|
5537
|
+
}
|
|
5538
|
+
writeSessionBreadcrumb(state) {
|
|
5539
|
+
try {
|
|
5540
|
+
localStorage.setItem(ACTIVE_SESSION_BREADCRUMB_KEY, JSON.stringify({
|
|
5541
|
+
sessionId: state.sessionId,
|
|
5542
|
+
isRecording: state.isRecording,
|
|
5543
|
+
updatedAt: state.updatedAt
|
|
5544
|
+
}));
|
|
5545
|
+
} catch {
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
flushActiveSessionOnDisconnect() {
|
|
5549
|
+
const snap = this.recording.getSessionSnapshot();
|
|
5550
|
+
if (!snap.isRecording) return;
|
|
5551
|
+
if (this.sessionSaveTimer) clearTimeout(this.sessionSaveTimer);
|
|
5552
|
+
this.writeSessionBreadcrumb(snap);
|
|
5553
|
+
this.persistence.saveActiveSession(snap).catch(() => {
|
|
5554
|
+
});
|
|
5555
|
+
}
|
|
5556
|
+
clearSessionPersistence() {
|
|
5557
|
+
if (this.sessionSaveTimer) {
|
|
5558
|
+
clearTimeout(this.sessionSaveTimer);
|
|
5559
|
+
this.sessionSaveTimer = void 0;
|
|
5560
|
+
}
|
|
5561
|
+
try {
|
|
5562
|
+
localStorage.removeItem(ACTIVE_SESSION_BREADCRUMB_KEY);
|
|
5563
|
+
} catch {
|
|
5564
|
+
}
|
|
5565
|
+
this.persistence.clearActiveSession().catch(() => {
|
|
5566
|
+
});
|
|
5567
|
+
}
|
|
5568
|
+
/** True when a synchronous breadcrumb marks an active recording session. */
|
|
5569
|
+
hasActiveSession() {
|
|
5570
|
+
try {
|
|
5571
|
+
const raw = localStorage.getItem(ACTIVE_SESSION_BREADCRUMB_KEY);
|
|
5572
|
+
if (!raw) return false;
|
|
5573
|
+
return !!JSON.parse(raw).isRecording;
|
|
5574
|
+
} catch {
|
|
5575
|
+
return false;
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
/** Programmatically resume the persisted session, if any. */
|
|
5579
|
+
resumeSession() {
|
|
5580
|
+
this.persistence.getActiveSession().then((s) => {
|
|
5581
|
+
if (s) this.resumeSessionState(s);
|
|
5582
|
+
});
|
|
5583
|
+
}
|
|
5584
|
+
/** Discard the persisted live session (breadcrumb + DB record). */
|
|
5585
|
+
discardSession() {
|
|
5586
|
+
this.clearSessionPersistence();
|
|
5587
|
+
}
|
|
4562
5588
|
toggle() {
|
|
4563
5589
|
this.recording.toggleRecording();
|
|
4564
5590
|
}
|
|
@@ -4572,6 +5598,12 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4572
5598
|
handleKeyboardEvent(event) {
|
|
4573
5599
|
if (!event.ctrlKey) return;
|
|
4574
5600
|
const key = event.key.toLowerCase();
|
|
5601
|
+
if (key === "e" && event.shiftKey) {
|
|
5602
|
+
event.preventDefault();
|
|
5603
|
+
this.toggleVisibility();
|
|
5604
|
+
return;
|
|
5605
|
+
}
|
|
5606
|
+
if (!this.isVisible) return;
|
|
4575
5607
|
if (key === "r") {
|
|
4576
5608
|
event.preventDefault();
|
|
4577
5609
|
this.toggle();
|
|
@@ -4589,6 +5621,10 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4589
5621
|
this.showSettingsDialog();
|
|
4590
5622
|
}
|
|
4591
5623
|
}
|
|
5624
|
+
toggleVisibility() {
|
|
5625
|
+
this.isVisible = !this.isVisible;
|
|
5626
|
+
this.style.display = this.isVisible ? "" : "none";
|
|
5627
|
+
}
|
|
4592
5628
|
// ── recording history (task 5) ────────────────────────────────────────────
|
|
4593
5629
|
saveRecordingHistory() {
|
|
4594
5630
|
if (!this.cypressCommands.length) return;
|
|
@@ -4824,13 +5860,13 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4824
5860
|
child.translation = this.translation;
|
|
4825
5861
|
container.appendChild(child);
|
|
4826
5862
|
child.addEventListener("savetest", (e) => {
|
|
4827
|
-
const { description, tags } = e.detail ?? {};
|
|
4828
|
-
this.onSaveTest(description ?? null, tags ?? []);
|
|
5863
|
+
const { description, notes, tags } = e.detail ?? {};
|
|
5864
|
+
this.onSaveTest(description ?? null, tags ?? [], notes ?? "");
|
|
4829
5865
|
import_sweetalert2.default.close();
|
|
4830
5866
|
});
|
|
4831
5867
|
child.addEventListener("saveandexport", (e) => {
|
|
4832
|
-
const { description, tags } = e.detail ?? {};
|
|
4833
|
-
this.onSaveAndExportTest(description ?? null, tags ?? []);
|
|
5868
|
+
const { description, notes, tags } = e.detail ?? {};
|
|
5869
|
+
this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "");
|
|
4834
5870
|
import_sweetalert2.default.close();
|
|
4835
5871
|
});
|
|
4836
5872
|
},
|
|
@@ -4861,6 +5897,10 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4861
5897
|
child.addEventListener("smartselectorchange", (e) => {
|
|
4862
5898
|
this.smartSelectorEnabled = e.detail;
|
|
4863
5899
|
});
|
|
5900
|
+
child.addEventListener("starthiddenchange", (e) => {
|
|
5901
|
+
this.isVisible = !e.detail;
|
|
5902
|
+
this.style.display = this.isVisible ? "" : "none";
|
|
5903
|
+
});
|
|
4864
5904
|
},
|
|
4865
5905
|
willClose: () => {
|
|
4866
5906
|
this.isSettingsDialogOpen = false;
|
|
@@ -4915,7 +5955,8 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4915
5955
|
e.detail.fileName,
|
|
4916
5956
|
e.detail.testId,
|
|
4917
5957
|
e.detail.itBlock,
|
|
4918
|
-
e.detail.interceptorsBlock
|
|
5958
|
+
e.detail.interceptorsBlock,
|
|
5959
|
+
e.detail.notes
|
|
4919
5960
|
), 150);
|
|
4920
5961
|
});
|
|
4921
5962
|
},
|
|
@@ -4929,7 +5970,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4929
5970
|
}, 0);
|
|
4930
5971
|
});
|
|
4931
5972
|
}
|
|
4932
|
-
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "") {
|
|
5973
|
+
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "", notes = "") {
|
|
4933
5974
|
import_sweetalert2.default.fire({
|
|
4934
5975
|
title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
|
|
4935
5976
|
html: '<div id="file-editor-modal-content" style="padding:0;height:540px"></div>',
|
|
@@ -4950,6 +5991,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4950
5991
|
child.closeLabel = this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR");
|
|
4951
5992
|
child.itBlock = itBlock;
|
|
4952
5993
|
child.interceptorsBlock = interceptorsBlock;
|
|
5994
|
+
child.notes = notes;
|
|
4953
5995
|
container.appendChild(child);
|
|
4954
5996
|
child.addEventListener("close", () => {
|
|
4955
5997
|
import_sweetalert2.default.close();
|
|
@@ -4972,18 +6014,18 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4972
6014
|
this.resizePopup();
|
|
4973
6015
|
}
|
|
4974
6016
|
// ── save handlers ─────────────────────────────────────────────────────────
|
|
4975
|
-
async onSaveTest(description, tags = []) {
|
|
6017
|
+
async onSaveTest(description, tags = [], notes = "") {
|
|
4976
6018
|
if (description) {
|
|
4977
|
-
await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags);
|
|
6019
|
+
await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes);
|
|
4978
6020
|
this.recording.clearCommands();
|
|
4979
6021
|
this.clearRecordingHistory();
|
|
4980
6022
|
this.cypressCommands = [];
|
|
4981
6023
|
this.interceptors = [];
|
|
4982
6024
|
}
|
|
4983
6025
|
}
|
|
4984
|
-
async onSaveAndExportTest(description, tags = []) {
|
|
6026
|
+
async onSaveAndExportTest(description, tags = [], notes = "") {
|
|
4985
6027
|
if (description) {
|
|
4986
|
-
const id = await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags);
|
|
6028
|
+
const id = await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes);
|
|
4987
6029
|
this.recording.clearCommands();
|
|
4988
6030
|
this.clearRecordingHistory();
|
|
4989
6031
|
this.cypressCommands = [];
|
|
@@ -5010,6 +6052,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5010
6052
|
render() {
|
|
5011
6053
|
const rec = this.isRecording;
|
|
5012
6054
|
const paused = this.isPaused;
|
|
6055
|
+
this.style.display = this.isVisible ? "" : "none";
|
|
5013
6056
|
this.shadow.innerHTML = `<style>${getRecorderStyles(rec, paused)}</style>${renderRecorderWidget(rec, paused, this.translation.translate.bind(this.translation))}`;
|
|
5014
6057
|
this.shadow.querySelector('[data-action="toggle"]')?.addEventListener("click", () => this.toggle());
|
|
5015
6058
|
this.shadow.querySelector('[data-action="pause"]')?.addEventListener("click", () => this.togglePause());
|
|
@@ -5024,24 +6067,29 @@ if (!customElements.get("lib-e2e-recorder")) {
|
|
|
5024
6067
|
}
|
|
5025
6068
|
|
|
5026
6069
|
// src/index.ts
|
|
5027
|
-
var VERSION = "0.
|
|
6070
|
+
var VERSION = "0.5.0";
|
|
5028
6071
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5029
6072
|
0 && (module.exports = {
|
|
6073
|
+
ACTIVE_SESSION_BREADCRUMB_KEY,
|
|
5030
6074
|
AdvancedTestEditorElement,
|
|
5031
6075
|
AdvancedTestTransformationService,
|
|
5032
6076
|
ConfigurationElement,
|
|
5033
6077
|
DB_SCHEMA,
|
|
5034
6078
|
DB_STORE_NAMES,
|
|
6079
|
+
DEFAULT_RESUME_TTL_MINUTES,
|
|
5035
6080
|
FilePreviewElement,
|
|
5036
6081
|
HttpMonitor,
|
|
5037
6082
|
INPUT_TYPES,
|
|
5038
6083
|
LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
|
|
6084
|
+
LOCALE_BY_LANG,
|
|
5039
6085
|
LibE2eRecorderElement,
|
|
5040
6086
|
PersistenceService,
|
|
6087
|
+
RESUME_TTL_CONFIG_KEY,
|
|
5041
6088
|
RecordingService,
|
|
5042
6089
|
SCROLLBAR_STYLES,
|
|
5043
6090
|
SUPPORTED_LANGS,
|
|
5044
6091
|
SaveTestElement,
|
|
6092
|
+
SelectorPickerElement,
|
|
5045
6093
|
Subject,
|
|
5046
6094
|
TestEditorElement,
|
|
5047
6095
|
TestPrevisualizerElement,
|
|
@@ -5052,10 +6100,13 @@ var VERSION = "0.1.0";
|
|
|
5052
6100
|
generateAlias,
|
|
5053
6101
|
injectStyles,
|
|
5054
6102
|
isLang,
|
|
6103
|
+
isLocalHost,
|
|
6104
|
+
localeForLang,
|
|
5055
6105
|
makeModalResizable,
|
|
5056
6106
|
makeSwalDraggable,
|
|
5057
6107
|
makeSwalDraggableByContentId,
|
|
5058
6108
|
persistenceService,
|
|
6109
|
+
selectTestsForExport,
|
|
5059
6110
|
setSwal2DataCyAttribute,
|
|
5060
6111
|
showToast,
|
|
5061
6112
|
transformationService,
|