lib-e2e-cypress-for-dummys-ts 0.3.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 -545
- package/dist/{selector-picker-VJOLGZ5H.js → chunk-FDMUIHDZ.js} +95 -38
- package/dist/chunk-FDMUIHDZ.js.map +1 -0
- package/dist/index.cjs +925 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -47
- package/dist/index.d.ts +226 -47
- package/dist/index.js +864 -93
- 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: {
|
|
@@ -590,8 +648,6 @@ var I18N_ES = {
|
|
|
590
648
|
COPY_DESCRIBE: "\u{1F5C2} Copiar describe()",
|
|
591
649
|
MULTI_SELECT: "\u{1F5C2} Multi-select",
|
|
592
650
|
CANCEL_SELECT: "\u2715 Cancelar",
|
|
593
|
-
SECTION_COMMANDS: "Comandos",
|
|
594
|
-
SECTION_INTERCEPTORS: "Interceptores",
|
|
595
651
|
COPY_CMDS_BTN: "\u{1F4CB} Copiar comandos",
|
|
596
652
|
COPY_ICPS_BTN: "\u{1F4CB} Copiar interceptores",
|
|
597
653
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
@@ -620,6 +676,18 @@ var I18N_ES = {
|
|
|
620
676
|
DATA_SECTION: "\u{1F4BE} Datos",
|
|
621
677
|
DATA_DESC: "Exporta todos tus tests a JSON o importa una copia de seguridad.",
|
|
622
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:",
|
|
623
691
|
IMPORT_BTN: "\u2B07\uFE0F Importar tests",
|
|
624
692
|
FOLDER_UPDATED_TOAST: "\u2713 Carpeta de Cypress actualizada",
|
|
625
693
|
FOLDER_ERROR_TOAST: "Error al acceder a la carpeta",
|
|
@@ -632,7 +700,10 @@ var I18N_ES = {
|
|
|
632
700
|
SMART_SELECTOR_SUB: "Muestra un picker al hacer click en elementos sin selector v\xE1lido.",
|
|
633
701
|
START_HIDDEN_SECTION: "\u{1F441} Visibilidad del widget",
|
|
634
702
|
START_HIDDEN_TITLE: "Iniciar oculto",
|
|
635
|
-
START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo."
|
|
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."
|
|
636
707
|
},
|
|
637
708
|
SELECTOR_PICKER: {
|
|
638
709
|
TITLE: "Selecciona un elemento del DOM",
|
|
@@ -673,8 +744,10 @@ var I18N_ES = {
|
|
|
673
744
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Editar manualmente",
|
|
674
745
|
CLOSE_BTN: "\u2715 Cerrar",
|
|
675
746
|
NEW_FILE_BTN: "+ Nuevo archivo",
|
|
747
|
+
NEW_FOLDER_BTN: "+ Nueva carpeta",
|
|
676
748
|
REFRESH_BTN: "\u21BB Actualizar",
|
|
677
749
|
NEW_FILE_PLACEHOLDER: "nombre-del-test",
|
|
750
|
+
NEW_FOLDER_PLACEHOLDER: "nombre-de-carpeta",
|
|
678
751
|
NEW_FILE_CONFIRM: "Crear",
|
|
679
752
|
NEW_FILE_CANCEL: "Cancelar"
|
|
680
753
|
},
|
|
@@ -687,10 +760,19 @@ var I18N_ES = {
|
|
|
687
760
|
NO_CHANGES: "Sin cambios respecto al original",
|
|
688
761
|
NO_CHANGES_SHORT: "Sin cambios",
|
|
689
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?)",
|
|
690
768
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
691
769
|
SAVE_BTN: "\u{1F4BE} Guardar",
|
|
692
770
|
CLOSE_BTN: "\u2715 Cerrar",
|
|
693
|
-
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"
|
|
694
776
|
},
|
|
695
777
|
RECORDER: {
|
|
696
778
|
FS_TITLE: "\u{1F4C1} Acceso a ficheros",
|
|
@@ -719,7 +801,12 @@ var I18N_ES = {
|
|
|
719
801
|
BADGE_PAUSED: "\u23F8 PAUSA",
|
|
720
802
|
BADGE_REC: "\u25CF REC",
|
|
721
803
|
STOP_TITLE: "Detener (Ctrl+R)",
|
|
722
|
-
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"
|
|
723
810
|
}
|
|
724
811
|
};
|
|
725
812
|
|
|
@@ -757,8 +844,6 @@ var I18N_EN = {
|
|
|
757
844
|
COPY_DESCRIBE: "\u{1F5C2} Copy describe()",
|
|
758
845
|
MULTI_SELECT: "\u{1F5C2} Multi-select",
|
|
759
846
|
CANCEL_SELECT: "\u2715 Cancel",
|
|
760
|
-
SECTION_COMMANDS: "Commands",
|
|
761
|
-
SECTION_INTERCEPTORS: "Interceptors",
|
|
762
847
|
COPY_CMDS_BTN: "\u{1F4CB} Copy commands",
|
|
763
848
|
COPY_ICPS_BTN: "\u{1F4CB} Copy interceptors",
|
|
764
849
|
DEFAULT_DESCRIBE: "Test suite",
|
|
@@ -787,6 +872,18 @@ var I18N_EN = {
|
|
|
787
872
|
DATA_SECTION: "\u{1F4BE} Data",
|
|
788
873
|
DATA_DESC: "Export all your tests to JSON or import a backup.",
|
|
789
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:",
|
|
790
887
|
IMPORT_BTN: "\u2B07\uFE0F Import tests",
|
|
791
888
|
FOLDER_UPDATED_TOAST: "\u2713 Cypress folder updated",
|
|
792
889
|
FOLDER_ERROR_TOAST: "Error accessing the folder",
|
|
@@ -799,7 +896,10 @@ var I18N_EN = {
|
|
|
799
896
|
SMART_SELECTOR_SUB: "Shows a picker when clicking elements with no valid selector.",
|
|
800
897
|
START_HIDDEN_SECTION: "\u{1F441} Widget visibility",
|
|
801
898
|
START_HIDDEN_TITLE: "Start hidden",
|
|
802
|
-
START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it."
|
|
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."
|
|
803
903
|
},
|
|
804
904
|
SELECTOR_PICKER: {
|
|
805
905
|
TITLE: "Select a DOM element",
|
|
@@ -840,8 +940,10 @@ var I18N_EN = {
|
|
|
840
940
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Edit manually",
|
|
841
941
|
CLOSE_BTN: "\u2715 Close",
|
|
842
942
|
NEW_FILE_BTN: "+ New file",
|
|
943
|
+
NEW_FOLDER_BTN: "+ New folder",
|
|
843
944
|
REFRESH_BTN: "\u21BB Refresh",
|
|
844
945
|
NEW_FILE_PLACEHOLDER: "test-name",
|
|
946
|
+
NEW_FOLDER_PLACEHOLDER: "folder-name",
|
|
845
947
|
NEW_FILE_CONFIRM: "Create",
|
|
846
948
|
NEW_FILE_CANCEL: "Cancel"
|
|
847
949
|
},
|
|
@@ -854,10 +956,19 @@ var I18N_EN = {
|
|
|
854
956
|
NO_CHANGES: "No changes from original",
|
|
855
957
|
NO_CHANGES_SHORT: "No changes",
|
|
856
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?)",
|
|
857
964
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
858
965
|
SAVE_BTN: "\u{1F4BE} Save",
|
|
859
966
|
CLOSE_BTN: "\u2715 Close",
|
|
860
|
-
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"
|
|
861
972
|
},
|
|
862
973
|
RECORDER: {
|
|
863
974
|
FS_TITLE: "\u{1F4C1} File access",
|
|
@@ -886,7 +997,12 @@ var I18N_EN = {
|
|
|
886
997
|
BADGE_PAUSED: "\u23F8 PAUSED",
|
|
887
998
|
BADGE_REC: "\u25CF REC",
|
|
888
999
|
STOP_TITLE: "Stop (Ctrl+R)",
|
|
889
|
-
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"
|
|
890
1006
|
}
|
|
891
1007
|
};
|
|
892
1008
|
|
|
@@ -924,8 +1040,6 @@ var I18N_FR = {
|
|
|
924
1040
|
COPY_DESCRIBE: "\u{1F5C2} Copier describe()",
|
|
925
1041
|
MULTI_SELECT: "\u{1F5C2} Multi-s\xE9lection",
|
|
926
1042
|
CANCEL_SELECT: "\u2715 Annuler",
|
|
927
|
-
SECTION_COMMANDS: "Commandes",
|
|
928
|
-
SECTION_INTERCEPTORS: "Intercepteurs",
|
|
929
1043
|
COPY_CMDS_BTN: "\u{1F4CB} Copier les commandes",
|
|
930
1044
|
COPY_ICPS_BTN: "\u{1F4CB} Copier les intercepteurs",
|
|
931
1045
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
@@ -954,6 +1068,18 @@ var I18N_FR = {
|
|
|
954
1068
|
DATA_SECTION: "\u{1F4BE} Donn\xE9es",
|
|
955
1069
|
DATA_DESC: "Exportez tous vos tests en JSON ou importez une sauvegarde.",
|
|
956
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 :",
|
|
957
1083
|
IMPORT_BTN: "\u2B07\uFE0F Importer les tests",
|
|
958
1084
|
FOLDER_UPDATED_TOAST: "\u2713 Dossier Cypress mis \xE0 jour",
|
|
959
1085
|
FOLDER_ERROR_TOAST: "Erreur lors de l'acc\xE8s au dossier",
|
|
@@ -966,7 +1092,10 @@ var I18N_FR = {
|
|
|
966
1092
|
SMART_SELECTOR_SUB: "Affiche un s\xE9lecteur lors du clic sur des \xE9l\xE9ments sans s\xE9lecteur valide.",
|
|
967
1093
|
START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE9 du widget",
|
|
968
1094
|
START_HIDDEN_TITLE: "D\xE9marrer masqu\xE9",
|
|
969
|
-
START_HIDDEN_SUB: "Le widget d\xE9marre invisible. Utilisez Ctrl+Shift+E pour l'afficher ou le masquer."
|
|
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."
|
|
970
1099
|
},
|
|
971
1100
|
SELECTOR_PICKER: {
|
|
972
1101
|
TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
|
|
@@ -1007,8 +1136,10 @@ var I18N_FR = {
|
|
|
1007
1136
|
EDIT_MANUAL_BTN: "\u270F\uFE0F \xC9diter manuellement",
|
|
1008
1137
|
CLOSE_BTN: "\u2715 Fermer",
|
|
1009
1138
|
NEW_FILE_BTN: "+ Nouveau fichier",
|
|
1139
|
+
NEW_FOLDER_BTN: "+ Nouveau dossier",
|
|
1010
1140
|
REFRESH_BTN: "\u21BB Actualiser",
|
|
1011
1141
|
NEW_FILE_PLACEHOLDER: "nom-du-test",
|
|
1142
|
+
NEW_FOLDER_PLACEHOLDER: "nom-du-dossier",
|
|
1012
1143
|
NEW_FILE_CONFIRM: "Cr\xE9er",
|
|
1013
1144
|
NEW_FILE_CANCEL: "Annuler"
|
|
1014
1145
|
},
|
|
@@ -1021,10 +1152,19 @@ var I18N_FR = {
|
|
|
1021
1152
|
NO_CHANGES: "Aucun changement par rapport \xE0 l'original",
|
|
1022
1153
|
NO_CHANGES_SHORT: "Aucun changement",
|
|
1023
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 ?)",
|
|
1024
1160
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1025
1161
|
SAVE_BTN: "\u{1F4BE} Enregistrer",
|
|
1026
1162
|
CLOSE_BTN: "\u2715 Fermer",
|
|
1027
|
-
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"
|
|
1028
1168
|
},
|
|
1029
1169
|
RECORDER: {
|
|
1030
1170
|
FS_TITLE: "\u{1F4C1} Acc\xE8s aux fichiers",
|
|
@@ -1053,7 +1193,12 @@ var I18N_FR = {
|
|
|
1053
1193
|
BADGE_PAUSED: "\u23F8 PAUSE",
|
|
1054
1194
|
BADGE_REC: "\u25CF REC",
|
|
1055
1195
|
STOP_TITLE: "Arr\xEAter (Ctrl+R)",
|
|
1056
|
-
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"
|
|
1057
1202
|
}
|
|
1058
1203
|
};
|
|
1059
1204
|
|
|
@@ -1091,8 +1236,6 @@ var I18N_IT = {
|
|
|
1091
1236
|
COPY_DESCRIBE: "\u{1F5C2} Copia describe()",
|
|
1092
1237
|
MULTI_SELECT: "\u{1F5C2} Multi-selezione",
|
|
1093
1238
|
CANCEL_SELECT: "\u2715 Annulla",
|
|
1094
|
-
SECTION_COMMANDS: "Comandi",
|
|
1095
|
-
SECTION_INTERCEPTORS: "Interceptor",
|
|
1096
1239
|
COPY_CMDS_BTN: "\u{1F4CB} Copia comandi",
|
|
1097
1240
|
COPY_ICPS_BTN: "\u{1F4CB} Copia interceptor",
|
|
1098
1241
|
DEFAULT_DESCRIBE: "Suite di test",
|
|
@@ -1121,6 +1264,18 @@ var I18N_IT = {
|
|
|
1121
1264
|
DATA_SECTION: "\u{1F4BE} Dati",
|
|
1122
1265
|
DATA_DESC: "Esporta tutti i tuoi test in JSON o importa un backup.",
|
|
1123
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:",
|
|
1124
1279
|
IMPORT_BTN: "\u2B07\uFE0F Importa test",
|
|
1125
1280
|
FOLDER_UPDATED_TOAST: "\u2713 Cartella Cypress aggiornata",
|
|
1126
1281
|
FOLDER_ERROR_TOAST: "Errore durante l'accesso alla cartella",
|
|
@@ -1133,7 +1288,10 @@ var I18N_IT = {
|
|
|
1133
1288
|
SMART_SELECTOR_SUB: "Mostra un selettore quando si fa clic su elementi senza selettore valido.",
|
|
1134
1289
|
START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE0 del widget",
|
|
1135
1290
|
START_HIDDEN_TITLE: "Avvia nascosto",
|
|
1136
|
-
START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo."
|
|
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."
|
|
1137
1295
|
},
|
|
1138
1296
|
SELECTOR_PICKER: {
|
|
1139
1297
|
TITLE: "Seleziona un elemento DOM",
|
|
@@ -1174,8 +1332,10 @@ var I18N_IT = {
|
|
|
1174
1332
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Modifica manualmente",
|
|
1175
1333
|
CLOSE_BTN: "\u2715 Chiudi",
|
|
1176
1334
|
NEW_FILE_BTN: "+ Nuovo file",
|
|
1335
|
+
NEW_FOLDER_BTN: "+ Nuova cartella",
|
|
1177
1336
|
REFRESH_BTN: "\u21BB Aggiorna",
|
|
1178
1337
|
NEW_FILE_PLACEHOLDER: "nome-del-test",
|
|
1338
|
+
NEW_FOLDER_PLACEHOLDER: "nome-cartella",
|
|
1179
1339
|
NEW_FILE_CONFIRM: "Crea",
|
|
1180
1340
|
NEW_FILE_CANCEL: "Annulla"
|
|
1181
1341
|
},
|
|
@@ -1188,10 +1348,19 @@ var I18N_IT = {
|
|
|
1188
1348
|
NO_CHANGES: "Nessuna modifica rispetto all'originale",
|
|
1189
1349
|
NO_CHANGES_SHORT: "Nessuna modifica",
|
|
1190
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?)",
|
|
1191
1356
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1192
1357
|
SAVE_BTN: "\u{1F4BE} Salva",
|
|
1193
1358
|
CLOSE_BTN: "\u2715 Chiudi",
|
|
1194
|
-
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"
|
|
1195
1364
|
},
|
|
1196
1365
|
RECORDER: {
|
|
1197
1366
|
FS_TITLE: "\u{1F4C1} Accesso ai file",
|
|
@@ -1220,7 +1389,12 @@ var I18N_IT = {
|
|
|
1220
1389
|
BADGE_PAUSED: "\u23F8 PAUSA",
|
|
1221
1390
|
BADGE_REC: "\u25CF REC",
|
|
1222
1391
|
STOP_TITLE: "Ferma (Ctrl+R)",
|
|
1223
|
-
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"
|
|
1224
1398
|
}
|
|
1225
1399
|
};
|
|
1226
1400
|
|
|
@@ -1258,8 +1432,6 @@ var I18N_DE = {
|
|
|
1258
1432
|
COPY_DESCRIBE: "\u{1F5C2} describe() kopieren",
|
|
1259
1433
|
MULTI_SELECT: "\u{1F5C2} Mehrfachauswahl",
|
|
1260
1434
|
CANCEL_SELECT: "\u2715 Abbrechen",
|
|
1261
|
-
SECTION_COMMANDS: "Befehle",
|
|
1262
|
-
SECTION_INTERCEPTORS: "Interceptors",
|
|
1263
1435
|
COPY_CMDS_BTN: "\u{1F4CB} Befehle kopieren",
|
|
1264
1436
|
COPY_ICPS_BTN: "\u{1F4CB} Interceptors kopieren",
|
|
1265
1437
|
DEFAULT_DESCRIBE: "Test-Suite",
|
|
@@ -1288,6 +1460,18 @@ var I18N_DE = {
|
|
|
1288
1460
|
DATA_SECTION: "\u{1F4BE} Daten",
|
|
1289
1461
|
DATA_DESC: "Exportieren Sie alle Tests als JSON oder importieren Sie ein Backup.",
|
|
1290
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:",
|
|
1291
1475
|
IMPORT_BTN: "\u2B07\uFE0F Tests importieren",
|
|
1292
1476
|
FOLDER_UPDATED_TOAST: "\u2713 Cypress-Ordner aktualisiert",
|
|
1293
1477
|
FOLDER_ERROR_TOAST: "Fehler beim Zugriff auf den Ordner",
|
|
@@ -1300,7 +1484,10 @@ var I18N_DE = {
|
|
|
1300
1484
|
SMART_SELECTOR_SUB: "Zeigt ein Auswahlfeld, wenn auf Elemente ohne g\xFCltigen Selektor geklickt wird.",
|
|
1301
1485
|
START_HIDDEN_SECTION: "\u{1F441} Widget-Sichtbarkeit",
|
|
1302
1486
|
START_HIDDEN_TITLE: "Versteckt starten",
|
|
1303
|
-
START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden."
|
|
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."
|
|
1304
1491
|
},
|
|
1305
1492
|
SELECTOR_PICKER: {
|
|
1306
1493
|
TITLE: "DOM-Element ausw\xE4hlen",
|
|
@@ -1341,8 +1528,10 @@ var I18N_DE = {
|
|
|
1341
1528
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Manuell bearbeiten",
|
|
1342
1529
|
CLOSE_BTN: "\u2715 Schlie\xDFen",
|
|
1343
1530
|
NEW_FILE_BTN: "+ Neue Datei",
|
|
1531
|
+
NEW_FOLDER_BTN: "+ Neuer Ordner",
|
|
1344
1532
|
REFRESH_BTN: "\u21BB Aktualisieren",
|
|
1345
1533
|
NEW_FILE_PLACEHOLDER: "test-name",
|
|
1534
|
+
NEW_FOLDER_PLACEHOLDER: "ordnername",
|
|
1346
1535
|
NEW_FILE_CONFIRM: "Erstellen",
|
|
1347
1536
|
NEW_FILE_CANCEL: "Abbrechen"
|
|
1348
1537
|
},
|
|
@@ -1355,10 +1544,19 @@ var I18N_DE = {
|
|
|
1355
1544
|
NO_CHANGES: "Keine \xC4nderungen gegen\xFCber dem Original",
|
|
1356
1545
|
NO_CHANGES_SHORT: "Keine \xC4nderungen",
|
|
1357
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?)",
|
|
1358
1552
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1359
1553
|
SAVE_BTN: "\u{1F4BE} Speichern",
|
|
1360
1554
|
CLOSE_BTN: "\u2715 Schlie\xDFen",
|
|
1361
|
-
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"
|
|
1362
1560
|
},
|
|
1363
1561
|
RECORDER: {
|
|
1364
1562
|
FS_TITLE: "\u{1F4C1} Dateizugriff",
|
|
@@ -1387,13 +1585,18 @@ var I18N_DE = {
|
|
|
1387
1585
|
BADGE_PAUSED: "\u23F8 PAUSE",
|
|
1388
1586
|
BADGE_REC: "\u25CF REC",
|
|
1389
1587
|
STOP_TITLE: "Stopp (Ctrl+R)",
|
|
1390
|
-
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"
|
|
1391
1594
|
}
|
|
1392
1595
|
};
|
|
1393
1596
|
|
|
1394
1597
|
// src/services/translation.service.ts
|
|
1395
1598
|
var TranslationService = class {
|
|
1396
|
-
lang
|
|
1599
|
+
lang$;
|
|
1397
1600
|
translations = {
|
|
1398
1601
|
es: I18N_ES,
|
|
1399
1602
|
en: I18N_EN,
|
|
@@ -1402,17 +1605,21 @@ var TranslationService = class {
|
|
|
1402
1605
|
de: I18N_DE
|
|
1403
1606
|
};
|
|
1404
1607
|
constructor() {
|
|
1405
|
-
this.lang = this.detectLang();
|
|
1608
|
+
this.lang$ = new Subject(this.detectLang());
|
|
1406
1609
|
}
|
|
1407
1610
|
setLang(lang) {
|
|
1408
|
-
this.lang
|
|
1611
|
+
this.lang$.next(lang);
|
|
1409
1612
|
}
|
|
1410
1613
|
getLang() {
|
|
1411
|
-
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);
|
|
1412
1619
|
}
|
|
1413
1620
|
translate(key) {
|
|
1414
1621
|
const keys = key.split(".");
|
|
1415
|
-
let value = this.translations[this.lang];
|
|
1622
|
+
let value = this.translations[this.lang$.getValue()];
|
|
1416
1623
|
for (const k of keys) {
|
|
1417
1624
|
value = value?.[k];
|
|
1418
1625
|
if (value === void 0) return key;
|
|
@@ -1435,6 +1642,9 @@ function gcd(a, b) {
|
|
|
1435
1642
|
}
|
|
1436
1643
|
return a;
|
|
1437
1644
|
}
|
|
1645
|
+
function escapeSingleQuotes(value) {
|
|
1646
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1647
|
+
}
|
|
1438
1648
|
function normalizeBlock(code, baseIndent) {
|
|
1439
1649
|
const lines = code.split("\n").map((l) => l.trimEnd());
|
|
1440
1650
|
const nonEmpty = lines.filter((l) => l.trim().length > 0);
|
|
@@ -1457,7 +1667,7 @@ var TransformationService = class {
|
|
|
1457
1667
|
}
|
|
1458
1668
|
generateItDescription(description, commands) {
|
|
1459
1669
|
const commandsBlock = commands.map((cmd) => normalizeBlock(cmd, " ")).join("\n");
|
|
1460
|
-
return `it('${description}', () => {
|
|
1670
|
+
return `it('${escapeSingleQuotes(description)}', () => {
|
|
1461
1671
|
${commandsBlock}
|
|
1462
1672
|
});`;
|
|
1463
1673
|
}
|
|
@@ -1511,29 +1721,15 @@ ${lines}
|
|
|
1511
1721
|
};
|
|
1512
1722
|
var advancedTestTransformationService = new AdvancedTestTransformationService();
|
|
1513
1723
|
|
|
1514
|
-
// src/utils/subject.ts
|
|
1515
|
-
var Subject = class {
|
|
1516
|
-
_value;
|
|
1517
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1518
|
-
constructor(initialValue) {
|
|
1519
|
-
this._value = initialValue;
|
|
1520
|
-
}
|
|
1521
|
-
next(value) {
|
|
1522
|
-
this._value = value;
|
|
1523
|
-
this.listeners.forEach((l) => l(value));
|
|
1524
|
-
}
|
|
1525
|
-
getValue() {
|
|
1526
|
-
return this._value;
|
|
1527
|
-
}
|
|
1528
|
-
subscribe(fn) {
|
|
1529
|
-
this.listeners.add(fn);
|
|
1530
|
-
return () => this.listeners.delete(fn);
|
|
1531
|
-
}
|
|
1532
|
-
};
|
|
1533
|
-
|
|
1534
1724
|
// src/services/recording.service.ts
|
|
1535
1725
|
init_selector_quality_utils();
|
|
1536
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
|
+
}
|
|
1537
1733
|
var RecordingService = class {
|
|
1538
1734
|
commands$ = new Subject([]);
|
|
1539
1735
|
interceptors$ = new Subject([]);
|
|
@@ -1543,6 +1739,9 @@ var RecordingService = class {
|
|
|
1543
1739
|
inputDebounceTimers = /* @__PURE__ */ new Map();
|
|
1544
1740
|
abort = new AbortController();
|
|
1545
1741
|
selectorStrategy = "data-cy";
|
|
1742
|
+
/** Stable id of the current live session (null when none has started). */
|
|
1743
|
+
sessionId = null;
|
|
1744
|
+
startedAt = 0;
|
|
1546
1745
|
// Stored originals for history patching cleanup
|
|
1547
1746
|
origPushState = history.pushState.bind(history);
|
|
1548
1747
|
origReplaceState = history.replaceState.bind(history);
|
|
@@ -1554,12 +1753,42 @@ var RecordingService = class {
|
|
|
1554
1753
|
}
|
|
1555
1754
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
1556
1755
|
startRecording() {
|
|
1756
|
+
this.sessionId = createSessionId();
|
|
1757
|
+
this.startedAt = Date.now();
|
|
1557
1758
|
this.isPaused$.next(false);
|
|
1558
1759
|
this.isRecording$.next(true);
|
|
1559
1760
|
this.addCommand(`cy.viewport(1900, 1200)`);
|
|
1560
1761
|
this.addCommand(`cy.visit('${window.location.pathname}')`);
|
|
1561
1762
|
this.addCommand(`cy.get('[data-cy="lib-e2e-cypress-for-dummys"]').invoke('hide');`);
|
|
1562
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
|
+
}
|
|
1563
1792
|
stopRecording() {
|
|
1564
1793
|
this.isPaused$.next(false);
|
|
1565
1794
|
this.isRecording$.next(false);
|
|
@@ -1646,6 +1875,21 @@ var RecordingService = class {
|
|
|
1646
1875
|
onPauseChange(fn) {
|
|
1647
1876
|
return this.isPaused$.subscribe(fn);
|
|
1648
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
|
+
}
|
|
1649
1893
|
onSelectorNotFound(fn) {
|
|
1650
1894
|
return this.selectorNotFound$.subscribe((v) => {
|
|
1651
1895
|
if (v) fn(v.target, v.action);
|
|
@@ -1877,6 +2121,7 @@ var RecordingService = class {
|
|
|
1877
2121
|
|
|
1878
2122
|
// src/services/persistence.service.ts
|
|
1879
2123
|
var import_idb = require("idb");
|
|
2124
|
+
var ACTIVE_SESSION_ID = 1;
|
|
1880
2125
|
var PersistenceService = class {
|
|
1881
2126
|
// dbName is overridable so tests can use unique names for isolation.
|
|
1882
2127
|
constructor(dbName = DB_SCHEMA.name) {
|
|
@@ -1930,7 +2175,7 @@ var PersistenceService = class {
|
|
|
1930
2175
|
if (!record) return null;
|
|
1931
2176
|
const commands = await this.getCommandStrings(testId);
|
|
1932
2177
|
const interceptors = await this.getInterceptorStrings(testId);
|
|
1933
|
-
const itBlock = `it('${record.name}', () => {
|
|
2178
|
+
const itBlock = `it('${escapeSingleQuotes(record.name)}', () => {
|
|
1934
2179
|
${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
1935
2180
|
});`;
|
|
1936
2181
|
const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
|
|
@@ -2011,6 +2256,25 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
2011
2256
|
const records = await db.getAll("configuration");
|
|
2012
2257
|
return records[0] ?? null;
|
|
2013
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
|
+
}
|
|
2014
2278
|
// ── Bulk operations ───────────────────────────────────────────────────────
|
|
2015
2279
|
async clearAllData() {
|
|
2016
2280
|
const db = await this.getDB();
|
|
@@ -2021,10 +2285,18 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
2021
2285
|
]);
|
|
2022
2286
|
}
|
|
2023
2287
|
async ingestFileData(tests, interceptors) {
|
|
2024
|
-
|
|
2025
|
-
this.
|
|
2026
|
-
|
|
2027
|
-
|
|
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);
|
|
2028
2300
|
}
|
|
2029
2301
|
async requestDirectoryPermissions() {
|
|
2030
2302
|
if (!("showDirectoryPicker" in window)) {
|
|
@@ -2467,6 +2739,28 @@ function showToast(message, isSuccess = true) {
|
|
|
2467
2739
|
setTimeout(() => toast.remove(), 3e3);
|
|
2468
2740
|
}
|
|
2469
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
|
+
|
|
2470
2764
|
// src/components/test-previsualizer/test-previsualizer.styles.ts
|
|
2471
2765
|
var TEST_PREVISUALIZER_STYLES = `
|
|
2472
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; }
|
|
@@ -3069,7 +3363,7 @@ function highlightLine(line) {
|
|
|
3069
3363
|
|
|
3070
3364
|
// src/components/test-editor/test-editor.template.ts
|
|
3071
3365
|
function renderTestEditor(state, t) {
|
|
3072
|
-
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest } = state;
|
|
3366
|
+
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest, locale } = state;
|
|
3073
3367
|
const tagFilterHtml = tags.length ? `<div class="tag-filter">
|
|
3074
3368
|
${tags.map((tag) => `<button class="tag-chip${activeTag === tag ? " active" : ""}" data-filter-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}
|
|
3075
3369
|
</div>` : `<div class="tag-filter" style="color:#484f58;font-size:11px">${t("TEST_EDITOR.NO_TAGS")}</div>`;
|
|
@@ -3080,12 +3374,12 @@ function renderTestEditor(state, t) {
|
|
|
3080
3374
|
</div>` : "";
|
|
3081
3375
|
const rows = visible.map((test, i) => {
|
|
3082
3376
|
const expanded = expandedIndex === i;
|
|
3083
|
-
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" });
|
|
3084
3378
|
const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
|
|
3085
3379
|
const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
|
|
3086
3380
|
const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
|
|
3087
3381
|
const hasIcps = (test.interceptors ?? []).length > 0;
|
|
3088
|
-
const itBlockCode = `it('${test.name}', () => {
|
|
3382
|
+
const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
|
|
3089
3383
|
${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
3090
3384
|
});`;
|
|
3091
3385
|
const icpBlockCode = icps.length ? `beforeEach(() => {
|
|
@@ -3193,11 +3487,11 @@ var TestEditorElement = class extends HTMLElement {
|
|
|
3193
3487
|
` : "";
|
|
3194
3488
|
const itBlocks = selected.map((t) => {
|
|
3195
3489
|
const cmds = (t.commands ?? []).map((c) => ` ${c}`).join("\n");
|
|
3196
|
-
return ` it('${(t.name ?? "")
|
|
3490
|
+
return ` it('${escapeSingleQuotes(t.name ?? "")}', () => {
|
|
3197
3491
|
${cmds}
|
|
3198
3492
|
});`;
|
|
3199
3493
|
}).join("\n\n");
|
|
3200
|
-
const block = `describe('${name
|
|
3494
|
+
const block = `describe('${escapeSingleQuotes(name)}', () => {
|
|
3201
3495
|
${beforeEach}${itBlocks}
|
|
3202
3496
|
});`;
|
|
3203
3497
|
navigator.clipboard?.writeText(block);
|
|
@@ -3231,7 +3525,8 @@ ${beforeEach}${itBlocks}
|
|
|
3231
3525
|
selectedIds: this.selectedIds,
|
|
3232
3526
|
describeName: this.describeName,
|
|
3233
3527
|
expandedIndex: this.expandedIndex,
|
|
3234
|
-
interceptorsByTest: this.interceptorsByTest
|
|
3528
|
+
interceptorsByTest: this.interceptorsByTest,
|
|
3529
|
+
locale: localeForLang(this.translation.getLang())
|
|
3235
3530
|
}, this.t.bind(this))}`;
|
|
3236
3531
|
this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
|
|
3237
3532
|
this.shadow.querySelectorAll("[data-filter-tag]").forEach((el) => {
|
|
@@ -3388,9 +3683,75 @@ var CONFIGURATION_STYLES = `
|
|
|
3388
3683
|
|
|
3389
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 */
|
|
3390
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; }
|
|
3391
3751
|
`;
|
|
3392
3752
|
|
|
3393
3753
|
// src/components/configuration/configuration.template.ts
|
|
3754
|
+
init_html_utils();
|
|
3394
3755
|
var LANGS = [
|
|
3395
3756
|
{ value: "es", label: "Espa\xF1ol" },
|
|
3396
3757
|
{ value: "en", label: "English" },
|
|
@@ -3398,8 +3759,64 @@ var LANGS = [
|
|
|
3398
3759
|
{ value: "it", label: "Italiano" },
|
|
3399
3760
|
{ value: "de", label: "Deutsch" }
|
|
3400
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
|
+
}
|
|
3401
3818
|
function renderConfiguration(state, t) {
|
|
3402
|
-
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden } = state;
|
|
3819
|
+
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes } = state;
|
|
3403
3820
|
const langOptions = LANGS.map(
|
|
3404
3821
|
(l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
|
|
3405
3822
|
).join("");
|
|
@@ -3451,6 +3868,17 @@ function renderConfiguration(state, t) {
|
|
|
3451
3868
|
</label>
|
|
3452
3869
|
</div>
|
|
3453
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
|
+
|
|
3454
3882
|
<!-- Selector Strategy -->
|
|
3455
3883
|
<div class="card card-wide">
|
|
3456
3884
|
<div class="card-hd">${t("CONFIG.SELECTOR_SECTION")}</div>
|
|
@@ -3470,13 +3898,13 @@ function renderConfiguration(state, t) {
|
|
|
3470
3898
|
<div class="card card-wide">
|
|
3471
3899
|
<div class="card-hd">${t("CONFIG.FOLDER_SECTION")}</div>
|
|
3472
3900
|
<div class="fs-layout">
|
|
3473
|
-
<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>
|
|
3474
3902
|
\u2514\u2500\u2500 e2e/
|
|
3475
3903
|
\u2514\u2500\u2500 *.cy.ts</pre>
|
|
3476
3904
|
<div class="fs-right">
|
|
3477
3905
|
<div class="fs-status">
|
|
3478
3906
|
<span class="fs-dot ${filesystemGranted ? "on" : "off"}"></span>
|
|
3479
|
-
${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>`}
|
|
3480
3908
|
</div>
|
|
3481
3909
|
<div class="btn-row">
|
|
3482
3910
|
<button id="btn-change-folder">
|
|
@@ -3501,7 +3929,7 @@ function renderConfiguration(state, t) {
|
|
|
3501
3929
|
</div>
|
|
3502
3930
|
</div>
|
|
3503
3931
|
|
|
3504
|
-
</div
|
|
3932
|
+
</div>${renderExportOverlay(state, t)}`;
|
|
3505
3933
|
}
|
|
3506
3934
|
|
|
3507
3935
|
// src/components/configuration/configuration.ts
|
|
@@ -3514,6 +3942,12 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3514
3942
|
selectorStrategy = "data-cy";
|
|
3515
3943
|
smartSelectorEnabled = true;
|
|
3516
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();
|
|
3517
3951
|
filesystemGranted = false;
|
|
3518
3952
|
cypressFolderName = null;
|
|
3519
3953
|
constructor() {
|
|
@@ -3540,6 +3974,11 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3540
3974
|
this.selectorStrategy = config?.["selectorStrategy"] ?? "data-cy";
|
|
3541
3975
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
3542
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
|
+
}
|
|
3543
3982
|
this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
|
|
3544
3983
|
const handle = config?.["cypressDirectoryHandle"];
|
|
3545
3984
|
this.cypressFolderName = handle?.name ?? null;
|
|
@@ -3563,6 +4002,12 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3563
4002
|
this.dispatchEvent(new CustomEvent("starthiddenchange", { detail: checked, bubbles: true, composed: true }));
|
|
3564
4003
|
this.render();
|
|
3565
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
|
+
}
|
|
3566
4011
|
async onSmartSelectorChange(enabled) {
|
|
3567
4012
|
this.smartSelectorEnabled = enabled;
|
|
3568
4013
|
await this.persistence.setConfig({ smartSelectorEnabled: enabled ? "true" : "false" });
|
|
@@ -3592,8 +4037,7 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3592
4037
|
this.cypressFolderName = null;
|
|
3593
4038
|
this.render();
|
|
3594
4039
|
}
|
|
3595
|
-
|
|
3596
|
-
const tests = await this.persistence.getAllTests();
|
|
4040
|
+
downloadTests(tests) {
|
|
3597
4041
|
const blob = new Blob([JSON.stringify({ tests, interceptors: [] }, null, 2)], { type: "application/json" });
|
|
3598
4042
|
const url = URL.createObjectURL(blob);
|
|
3599
4043
|
const a = document.createElement("a");
|
|
@@ -3602,6 +4046,53 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3602
4046
|
a.click();
|
|
3603
4047
|
URL.revokeObjectURL(url);
|
|
3604
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
|
+
}
|
|
3605
4096
|
async importAllData(file) {
|
|
3606
4097
|
const text = await file.text();
|
|
3607
4098
|
let data;
|
|
@@ -3613,7 +4104,6 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3613
4104
|
if (!data || !Array.isArray(data.tests) || !Array.isArray(data.interceptors)) {
|
|
3614
4105
|
throw new Error(this.t("CONFIG.JSON_BAD_FORMAT"));
|
|
3615
4106
|
}
|
|
3616
|
-
await this.persistence.clearAllData();
|
|
3617
4107
|
await this.persistence.ingestFileData(data.tests, data.interceptors);
|
|
3618
4108
|
}
|
|
3619
4109
|
render() {
|
|
@@ -3624,7 +4114,13 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3624
4114
|
filesystemGranted: this.filesystemGranted,
|
|
3625
4115
|
cypressFolderName: this.cypressFolderName,
|
|
3626
4116
|
smartSelectorEnabled: this.smartSelectorEnabled,
|
|
3627
|
-
startHidden: this.startHidden
|
|
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
|
|
3628
4124
|
}, this.t.bind(this))}`;
|
|
3629
4125
|
;
|
|
3630
4126
|
this.shadow.getElementById("lang-select").addEventListener(
|
|
@@ -3643,13 +4139,28 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3643
4139
|
"change",
|
|
3644
4140
|
(e) => this.onStartHiddenChange(e.target.checked)
|
|
3645
4141
|
);
|
|
4142
|
+
this.shadow.getElementById("resume-ttl-input").addEventListener(
|
|
4143
|
+
"change",
|
|
4144
|
+
(e) => this.onResumeTtlChange(Number(e.target.value))
|
|
4145
|
+
);
|
|
3646
4146
|
this.shadow.getElementById("selector-strategy").addEventListener(
|
|
3647
4147
|
"change",
|
|
3648
4148
|
(e) => this.onSelectorStrategyChange(e.target.value)
|
|
3649
4149
|
);
|
|
3650
4150
|
this.shadow.getElementById("btn-change-folder")?.addEventListener("click", () => this.changeFolder());
|
|
3651
4151
|
this.shadow.getElementById("btn-revoke")?.addEventListener("click", () => this.revokeAccess());
|
|
3652
|
-
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
|
+
);
|
|
3653
4164
|
this.shadow.getElementById("file-input").addEventListener("change", async (e) => {
|
|
3654
4165
|
const file = e.target.files?.[0];
|
|
3655
4166
|
if (!file) return;
|
|
@@ -3759,18 +4270,21 @@ var ADVANCED_TEST_EDITOR_STYLES = `
|
|
|
3759
4270
|
}
|
|
3760
4271
|
.btn-copy:hover { background: #30363d; color: #e6edf3; }
|
|
3761
4272
|
.sidebar-toolbar {
|
|
3762
|
-
display: flex; gap: 4px; padding: 6px 6px 4px;
|
|
4273
|
+
display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 6px 4px;
|
|
3763
4274
|
border-bottom: 1px solid #21262d; flex-shrink: 0; background: #0d1117;
|
|
3764
4275
|
}
|
|
3765
4276
|
.btn-toolbar {
|
|
3766
|
-
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;
|
|
3767
4278
|
font-size: 10px; font-weight: 500; background: #161b22; color: #8b949e;
|
|
3768
4279
|
transition: background 0.12s, color 0.12s; white-space: nowrap; overflow: hidden;
|
|
3769
4280
|
text-overflow: ellipsis;
|
|
3770
4281
|
}
|
|
4282
|
+
.btn-toolbar.btn-full { flex: 1 1 100%; }
|
|
3771
4283
|
.btn-toolbar:hover { background: #30363d; color: #e6edf3; }
|
|
3772
4284
|
.btn-toolbar.btn-new { color: #3fb950; border-color: #238636; }
|
|
3773
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; }
|
|
3774
4288
|
.new-file-form {
|
|
3775
4289
|
padding: 6px; border-bottom: 1px solid #21262d; display: flex; flex-direction: column; gap: 4px;
|
|
3776
4290
|
}
|
|
@@ -3804,7 +4318,7 @@ function renderNoPermission(needsReauth, t) {
|
|
|
3804
4318
|
</div>`;
|
|
3805
4319
|
}
|
|
3806
4320
|
function renderAdvancedEditor(state, t) {
|
|
3807
|
-
const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, testNotes, saveButtonEnabled, isCreatingFile, collapsedDirs, sidebarWidth } = state;
|
|
4321
|
+
const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, testNotes, saveButtonEnabled, isCreatingFile, isCreatingFolder, collapsedDirs, sidebarWidth } = state;
|
|
3808
4322
|
const treeHtml = e2eTree.length ? renderTree(e2eTree, selectedFile, collapsedDirs) : `<div class="tree-item" style="color:#6c7a99">${t("ADVANCED_EDITOR.NO_FILES")}</div>`;
|
|
3809
4323
|
const contentHtml = selectedFileContent ? `<div class="file-name">\u{1F4C4} ${escHtml(selectedFile?.name ?? "")}</div>
|
|
3810
4324
|
<pre class="pre-file">${syntaxHighlight(selectedFileContent.slice(0, 6e3))}${selectedFileContent.length > 6e3 ? "\n..." : ""}</pre>` : `<div class="placeholder">${t("ADVANCED_EDITOR.SELECT_FILE")}</div>`;
|
|
@@ -3831,16 +4345,25 @@ function renderAdvancedEditor(state, t) {
|
|
|
3831
4345
|
<button id="btn-new-file-cancel" class="btn-cancel-form">${t("ADVANCED_EDITOR.NEW_FILE_CANCEL")}</button>
|
|
3832
4346
|
</div>
|
|
3833
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>` : "";
|
|
3834
4355
|
const toolbar = `
|
|
3835
4356
|
<div class="sidebar-toolbar">
|
|
3836
4357
|
<button id="btn-new-file" class="btn-toolbar btn-new">${t("ADVANCED_EDITOR.NEW_FILE_BTN")}</button>
|
|
3837
|
-
<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>
|
|
3838
4360
|
</div>`;
|
|
3839
4361
|
return `
|
|
3840
4362
|
<div class="layout">
|
|
3841
4363
|
<div class="sidebar" style="width:${sidebarWidth}px">
|
|
3842
4364
|
${toolbar}
|
|
3843
4365
|
${newFileForm}
|
|
4366
|
+
${newFolderForm}
|
|
3844
4367
|
<div class="tree-scroll">${treeHtml}</div>
|
|
3845
4368
|
</div>
|
|
3846
4369
|
<div id="resize-handle" class="resize-handle"></div>
|
|
@@ -3911,6 +4434,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3911
4434
|
previewFileName = null;
|
|
3912
4435
|
previewFileContent = null;
|
|
3913
4436
|
isCreatingFile = false;
|
|
4437
|
+
isCreatingFolder = false;
|
|
3914
4438
|
collapsedDirs = /* @__PURE__ */ new Set();
|
|
3915
4439
|
sidebarWidth = 220;
|
|
3916
4440
|
hasPermission = false;
|
|
@@ -3973,7 +4497,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3973
4497
|
const name = rawName.trim().replace(/\.cy\.ts$/, "");
|
|
3974
4498
|
if (!name) return;
|
|
3975
4499
|
const fileName = `${name}.cy.ts`;
|
|
3976
|
-
const template = `describe('${name}', () => {
|
|
4500
|
+
const template = `describe('${escapeSingleQuotes(name)}', () => {
|
|
3977
4501
|
|
|
3978
4502
|
it('should ', () => {
|
|
3979
4503
|
|
|
@@ -3991,6 +4515,17 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3991
4515
|
this.isCreatingFile = false;
|
|
3992
4516
|
await this.getFoldersData();
|
|
3993
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
|
+
}
|
|
3994
4529
|
async refreshTree() {
|
|
3995
4530
|
await this.getFoldersData();
|
|
3996
4531
|
}
|
|
@@ -4048,7 +4583,8 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
4048
4583
|
fileName: this.selectedFile?.name ?? "",
|
|
4049
4584
|
testId: this.testId,
|
|
4050
4585
|
itBlock: this.testItBlock,
|
|
4051
|
-
interceptorsBlock: this.interceptorsBlock
|
|
4586
|
+
interceptorsBlock: this.interceptorsBlock,
|
|
4587
|
+
notes: this.testNotes
|
|
4052
4588
|
},
|
|
4053
4589
|
bubbles: true,
|
|
4054
4590
|
composed: true
|
|
@@ -4125,6 +4661,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
4125
4661
|
testNotes: this.testNotes,
|
|
4126
4662
|
saveButtonEnabled: this.saveButtonEnabled,
|
|
4127
4663
|
isCreatingFile: this.isCreatingFile,
|
|
4664
|
+
isCreatingFolder: this.isCreatingFolder,
|
|
4128
4665
|
collapsedDirs: this.collapsedDirs,
|
|
4129
4666
|
sidebarWidth: this.sidebarWidth
|
|
4130
4667
|
}, this.t.bind(this))}`;
|
|
@@ -4148,9 +4685,16 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
4148
4685
|
this.shadow.getElementById("btn-close")?.addEventListener("click", () => this.closePreview());
|
|
4149
4686
|
this.shadow.getElementById("btn-new-file")?.addEventListener("click", () => {
|
|
4150
4687
|
this.isCreatingFile = !this.isCreatingFile;
|
|
4688
|
+
this.isCreatingFolder = false;
|
|
4151
4689
|
this.render();
|
|
4152
4690
|
if (this.isCreatingFile) this.shadow.getElementById("input-new-file")?.focus();
|
|
4153
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
|
+
});
|
|
4154
4698
|
this.shadow.getElementById("btn-refresh")?.addEventListener("click", () => this.refreshTree());
|
|
4155
4699
|
this.shadow.getElementById("btn-new-file-confirm")?.addEventListener("click", () => {
|
|
4156
4700
|
const input2 = this.shadow.getElementById("input-new-file");
|
|
@@ -4169,6 +4713,23 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
4169
4713
|
this.render();
|
|
4170
4714
|
}
|
|
4171
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
|
+
});
|
|
4172
4733
|
}
|
|
4173
4734
|
};
|
|
4174
4735
|
if (!customElements.get("advanced-test-editor")) {
|
|
@@ -4254,7 +4815,30 @@ var FILE_PREVIEW_STYLES = `
|
|
|
4254
4815
|
.btn-save:hover { background: #2ea043; border-color: #2ea043; }
|
|
4255
4816
|
.btn-launch { background: transparent; border-color: #e3b341; color: #e3b341; }
|
|
4256
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; }
|
|
4257
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
|
+
}
|
|
4258
4842
|
`;
|
|
4259
4843
|
|
|
4260
4844
|
// src/components/file-preview/file-preview.template.ts
|
|
@@ -4312,8 +4896,14 @@ function buildDiffHtml(original, current, t) {
|
|
|
4312
4896
|
return `<div class="diff-line ${type}"><span class="diff-sign">${sign}</span><span>${escHtml(line)}</span></div>`;
|
|
4313
4897
|
}).join("");
|
|
4314
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
|
+
};
|
|
4315
4905
|
function renderFilePreview(state, t) {
|
|
4316
|
-
const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel } = state;
|
|
4906
|
+
const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel, isLocal, runState, runOutput } = state;
|
|
4317
4907
|
const blocksPanelHtml = itBlock || interceptorsBlock ? `
|
|
4318
4908
|
<div class="blocks-panel">
|
|
4319
4909
|
${itBlock ? `
|
|
@@ -4334,6 +4924,13 @@ function renderFilePreview(state, t) {
|
|
|
4334
4924
|
</div>` : ""}
|
|
4335
4925
|
</div>` : "";
|
|
4336
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>` : "";
|
|
4337
4934
|
return `
|
|
4338
4935
|
<div class="container">
|
|
4339
4936
|
<div class="header">
|
|
@@ -4344,9 +4941,11 @@ function renderFilePreview(state, t) {
|
|
|
4344
4941
|
${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea>`}
|
|
4345
4942
|
${blocksPanelHtml}
|
|
4346
4943
|
</div>
|
|
4944
|
+
${runResultHtml}
|
|
4347
4945
|
<div class="footer">
|
|
4348
|
-
|
|
4946
|
+
${launchBtnHtml}
|
|
4349
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>` : ""}
|
|
4350
4949
|
<button id="btn-save" class="btn-save">${t("FILE_PREVIEW.SAVE_BTN")}</button>
|
|
4351
4950
|
<button id="btn-close">${escHtml(closeLabel || t("FILE_PREVIEW.CLOSE_BTN"))}</button>
|
|
4352
4951
|
</div>
|
|
@@ -4362,11 +4961,18 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4362
4961
|
translation = translationService;
|
|
4363
4962
|
itBlock = "";
|
|
4364
4963
|
interceptorsBlock = "";
|
|
4964
|
+
notes = "";
|
|
4365
4965
|
commands = [];
|
|
4366
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);
|
|
4367
4971
|
_fileContent = null;
|
|
4368
4972
|
_originalContent = null;
|
|
4369
4973
|
_showDiff = false;
|
|
4974
|
+
_runState = "idle";
|
|
4975
|
+
_runOutput = "";
|
|
4370
4976
|
constructor() {
|
|
4371
4977
|
super();
|
|
4372
4978
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -4394,15 +5000,34 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4394
5000
|
onClose() {
|
|
4395
5001
|
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
4396
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
|
+
*/
|
|
4397
5008
|
async launchTest(specPath) {
|
|
4398
|
-
|
|
5009
|
+
if (!this.isLocal) return;
|
|
5010
|
+
const path = specPath ?? this.fileName ?? "";
|
|
4399
5011
|
if (!path) return;
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
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();
|
|
4406
5031
|
}
|
|
4407
5032
|
copyToClipboard(text) {
|
|
4408
5033
|
navigator.clipboard?.writeText(text);
|
|
@@ -4411,6 +5036,32 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4411
5036
|
this._showDiff = !this._showDiff;
|
|
4412
5037
|
this.render();
|
|
4413
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
|
+
}
|
|
4414
5065
|
render() {
|
|
4415
5066
|
this.shadow.innerHTML = `<style>${FILE_PREVIEW_STYLES}</style>${renderFilePreview({
|
|
4416
5067
|
fileName: this.fileName,
|
|
@@ -4420,7 +5071,10 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4420
5071
|
currentContent: this.textarea?.value ?? this._fileContent ?? "",
|
|
4421
5072
|
itBlock: this.itBlock,
|
|
4422
5073
|
interceptorsBlock: this.interceptorsBlock,
|
|
4423
|
-
closeLabel: this.closeLabel
|
|
5074
|
+
closeLabel: this.closeLabel,
|
|
5075
|
+
isLocal: this.isLocal,
|
|
5076
|
+
runState: this._runState,
|
|
5077
|
+
runOutput: this._runOutput
|
|
4424
5078
|
}, this.t.bind(this))}`;
|
|
4425
5079
|
this.textarea = this.shadow.getElementById("editor");
|
|
4426
5080
|
if (this.textarea) {
|
|
@@ -4436,6 +5090,7 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4436
5090
|
this.copyToClipboard(this.textarea?.value ?? this._fileContent ?? "");
|
|
4437
5091
|
});
|
|
4438
5092
|
this.shadow.getElementById("btn-diff")?.addEventListener("click", () => this.toggleDiff());
|
|
5093
|
+
this.shadow.getElementById("btn-insert")?.addEventListener("click", () => this.insertBlocks());
|
|
4439
5094
|
this.shadow.getElementById("btn-copy-it")?.addEventListener("click", () => {
|
|
4440
5095
|
this.copyToClipboard(this.itBlock);
|
|
4441
5096
|
});
|
|
@@ -4683,6 +5338,9 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4683
5338
|
interceptorsUnsub;
|
|
4684
5339
|
pauseUnsub;
|
|
4685
5340
|
selectorNotFoundUnsub;
|
|
5341
|
+
langUnsub;
|
|
5342
|
+
sessionUnsub;
|
|
5343
|
+
sessionSaveTimer;
|
|
4686
5344
|
controlFirstTimeData = true;
|
|
4687
5345
|
_previsualizerRef = null;
|
|
4688
5346
|
httpMonitor;
|
|
@@ -4726,17 +5384,21 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4726
5384
|
this.initSubscriptions();
|
|
4727
5385
|
this.render();
|
|
4728
5386
|
this.initVisibility();
|
|
5387
|
+
this.initSessionContinuity();
|
|
4729
5388
|
this.keydownHandler = (e) => this.handleKeyboardEvent(e);
|
|
4730
5389
|
window.addEventListener("keydown", this.keydownHandler);
|
|
4731
5390
|
}
|
|
4732
5391
|
disconnectedCallback() {
|
|
4733
5392
|
if (this._isDisabled) return;
|
|
4734
5393
|
window.removeEventListener("keydown", this.keydownHandler);
|
|
5394
|
+
this.flushActiveSessionOnDisconnect();
|
|
4735
5395
|
this.recordingUnsub?.();
|
|
4736
5396
|
this.commandsUnsub?.();
|
|
4737
5397
|
this.interceptorsUnsub?.();
|
|
4738
5398
|
this.pauseUnsub?.();
|
|
4739
5399
|
this.selectorNotFoundUnsub?.();
|
|
5400
|
+
this.langUnsub?.();
|
|
5401
|
+
this.sessionUnsub?.();
|
|
4740
5402
|
this.httpMonitor?.uninstall();
|
|
4741
5403
|
this.recording.destroy();
|
|
4742
5404
|
}
|
|
@@ -4759,11 +5421,13 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4759
5421
|
this.isRecording = val;
|
|
4760
5422
|
if (!val && !this.controlFirstTimeData) {
|
|
4761
5423
|
this.saveRecordingHistory();
|
|
5424
|
+
this.clearSessionPersistence();
|
|
4762
5425
|
this.showSaveTestDialog();
|
|
4763
5426
|
}
|
|
4764
5427
|
this.controlFirstTimeData = false;
|
|
4765
5428
|
this.render();
|
|
4766
5429
|
});
|
|
5430
|
+
this.sessionUnsub = this.recording.onSessionChange((state) => this.persistActiveSession(state));
|
|
4767
5431
|
this.commandsUnsub = this.recording.onCommandsChange((cmds) => {
|
|
4768
5432
|
this.cypressCommands = cmds;
|
|
4769
5433
|
if (this._previsualizerRef) this._previsualizerRef.commands = cmds;
|
|
@@ -4779,6 +5443,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4779
5443
|
this.selectorNotFoundUnsub = this.recording.onSelectorNotFound((target) => {
|
|
4780
5444
|
if (this.smartSelectorEnabled) this.showSelectorPicker(target);
|
|
4781
5445
|
});
|
|
5446
|
+
this.langUnsub = this.translation.onLangChange(() => this.render());
|
|
4782
5447
|
}
|
|
4783
5448
|
showSelectorPicker(target) {
|
|
4784
5449
|
Promise.resolve().then(() => (init_selector_picker(), selector_picker_exports)).then(() => {
|
|
@@ -4807,6 +5472,119 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4807
5472
|
if (strategy) this.recording.selectorStrategy = strategy;
|
|
4808
5473
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
4809
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
|
+
}
|
|
4810
5588
|
toggle() {
|
|
4811
5589
|
this.recording.toggleRecording();
|
|
4812
5590
|
}
|
|
@@ -5177,7 +5955,8 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5177
5955
|
e.detail.fileName,
|
|
5178
5956
|
e.detail.testId,
|
|
5179
5957
|
e.detail.itBlock,
|
|
5180
|
-
e.detail.interceptorsBlock
|
|
5958
|
+
e.detail.interceptorsBlock,
|
|
5959
|
+
e.detail.notes
|
|
5181
5960
|
), 150);
|
|
5182
5961
|
});
|
|
5183
5962
|
},
|
|
@@ -5191,7 +5970,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5191
5970
|
}, 0);
|
|
5192
5971
|
});
|
|
5193
5972
|
}
|
|
5194
|
-
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "") {
|
|
5973
|
+
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "", notes = "") {
|
|
5195
5974
|
import_sweetalert2.default.fire({
|
|
5196
5975
|
title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
|
|
5197
5976
|
html: '<div id="file-editor-modal-content" style="padding:0;height:540px"></div>',
|
|
@@ -5212,6 +5991,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5212
5991
|
child.closeLabel = this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR");
|
|
5213
5992
|
child.itBlock = itBlock;
|
|
5214
5993
|
child.interceptorsBlock = interceptorsBlock;
|
|
5994
|
+
child.notes = notes;
|
|
5215
5995
|
container.appendChild(child);
|
|
5216
5996
|
child.addEventListener("close", () => {
|
|
5217
5997
|
import_sweetalert2.default.close();
|
|
@@ -5287,24 +6067,29 @@ if (!customElements.get("lib-e2e-recorder")) {
|
|
|
5287
6067
|
}
|
|
5288
6068
|
|
|
5289
6069
|
// src/index.ts
|
|
5290
|
-
var VERSION = "0.
|
|
6070
|
+
var VERSION = "0.5.0";
|
|
5291
6071
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5292
6072
|
0 && (module.exports = {
|
|
6073
|
+
ACTIVE_SESSION_BREADCRUMB_KEY,
|
|
5293
6074
|
AdvancedTestEditorElement,
|
|
5294
6075
|
AdvancedTestTransformationService,
|
|
5295
6076
|
ConfigurationElement,
|
|
5296
6077
|
DB_SCHEMA,
|
|
5297
6078
|
DB_STORE_NAMES,
|
|
6079
|
+
DEFAULT_RESUME_TTL_MINUTES,
|
|
5298
6080
|
FilePreviewElement,
|
|
5299
6081
|
HttpMonitor,
|
|
5300
6082
|
INPUT_TYPES,
|
|
5301
6083
|
LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
|
|
6084
|
+
LOCALE_BY_LANG,
|
|
5302
6085
|
LibE2eRecorderElement,
|
|
5303
6086
|
PersistenceService,
|
|
6087
|
+
RESUME_TTL_CONFIG_KEY,
|
|
5304
6088
|
RecordingService,
|
|
5305
6089
|
SCROLLBAR_STYLES,
|
|
5306
6090
|
SUPPORTED_LANGS,
|
|
5307
6091
|
SaveTestElement,
|
|
6092
|
+
SelectorPickerElement,
|
|
5308
6093
|
Subject,
|
|
5309
6094
|
TestEditorElement,
|
|
5310
6095
|
TestPrevisualizerElement,
|
|
@@ -5315,10 +6100,13 @@ var VERSION = "0.1.0";
|
|
|
5315
6100
|
generateAlias,
|
|
5316
6101
|
injectStyles,
|
|
5317
6102
|
isLang,
|
|
6103
|
+
isLocalHost,
|
|
6104
|
+
localeForLang,
|
|
5318
6105
|
makeModalResizable,
|
|
5319
6106
|
makeSwalDraggable,
|
|
5320
6107
|
makeSwalDraggableByContentId,
|
|
5321
6108
|
persistenceService,
|
|
6109
|
+
selectTestsForExport,
|
|
5322
6110
|
setSwal2DataCyAttribute,
|
|
5323
6111
|
showToast,
|
|
5324
6112
|
transformationService,
|