lib-e2e-cypress-for-dummys-ts 0.3.0 → 0.6.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 +654 -545
- package/dist/{selector-picker-VJOLGZ5H.js → chunk-FDMUIHDZ.js} +95 -38
- package/dist/chunk-FDMUIHDZ.js.map +1 -0
- package/dist/index.cjs +1133 -175
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +275 -47
- package/dist/index.d.ts +275 -47
- package/dist/index.js +1065 -131
- 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
|
-
const v = el.getAttribute(attr);
|
|
241
|
-
if (v) return `${attr}="${escHtml(v)}"`;
|
|
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
246
|
}
|
|
247
|
-
function
|
|
248
|
-
return
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
function escAttr(s) {
|
|
248
|
+
return s.replace(/"/g, """).replace(/</g, "<");
|
|
249
|
+
}
|
|
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,35 +460,50 @@ 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,
|
|
470
|
+
DRAG_THRESHOLD: () => DRAG_THRESHOLD,
|
|
462
471
|
FilePreviewElement: () => FilePreviewElement,
|
|
463
472
|
HttpMonitor: () => HttpMonitor,
|
|
464
473
|
INPUT_TYPES: () => INPUT_TYPES,
|
|
465
474
|
LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES: () => LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
|
|
475
|
+
LOCALE_BY_LANG: () => LOCALE_BY_LANG,
|
|
466
476
|
LibE2eRecorderElement: () => LibE2eRecorderElement,
|
|
467
477
|
PersistenceService: () => PersistenceService,
|
|
478
|
+
RESUME_TTL_CONFIG_KEY: () => RESUME_TTL_CONFIG_KEY,
|
|
468
479
|
RecordingService: () => RecordingService,
|
|
469
480
|
SCROLLBAR_STYLES: () => SCROLLBAR_STYLES,
|
|
470
481
|
SUPPORTED_LANGS: () => SUPPORTED_LANGS,
|
|
471
482
|
SaveTestElement: () => SaveTestElement,
|
|
483
|
+
SelectorPickerElement: () => SelectorPickerElement,
|
|
472
484
|
Subject: () => Subject,
|
|
485
|
+
TOGGLE_MARGIN: () => TOGGLE_MARGIN,
|
|
473
486
|
TestEditorElement: () => TestEditorElement,
|
|
474
487
|
TestPrevisualizerElement: () => TestPrevisualizerElement,
|
|
475
488
|
TransformationService: () => TransformationService,
|
|
476
489
|
TranslationService: () => TranslationService,
|
|
477
490
|
VERSION: () => VERSION,
|
|
478
491
|
advancedTestTransformationService: () => advancedTestTransformationService,
|
|
492
|
+
boxOffsetForDirection: () => boxOffsetForDirection,
|
|
493
|
+
boxTopLeftFor: () => boxTopLeftFor,
|
|
494
|
+
clampTogglePosition: () => clampTogglePosition,
|
|
495
|
+
defaultTogglePosition: () => defaultTogglePosition,
|
|
479
496
|
generateAlias: () => generateAlias,
|
|
480
497
|
injectStyles: () => injectStyles,
|
|
481
498
|
isLang: () => isLang,
|
|
499
|
+
isLocalHost: () => isLocalHost,
|
|
500
|
+
localeForLang: () => localeForLang,
|
|
482
501
|
makeModalResizable: () => makeModalResizable,
|
|
483
502
|
makeSwalDraggable: () => makeSwalDraggable,
|
|
484
503
|
makeSwalDraggableByContentId: () => makeSwalDraggableByContentId,
|
|
485
504
|
persistenceService: () => persistenceService,
|
|
505
|
+
resolveExpandDirection: () => resolveExpandDirection,
|
|
506
|
+
selectTestsForExport: () => selectTestsForExport,
|
|
486
507
|
setSwal2DataCyAttribute: () => setSwal2DataCyAttribute,
|
|
487
508
|
showToast: () => showToast,
|
|
488
509
|
transformationService: () => transformationService,
|
|
@@ -495,6 +516,16 @@ var SUPPORTED_LANGS = ["es", "en", "fr", "it", "de"];
|
|
|
495
516
|
function isLang(value) {
|
|
496
517
|
return SUPPORTED_LANGS.includes(value);
|
|
497
518
|
}
|
|
519
|
+
var LOCALE_BY_LANG = {
|
|
520
|
+
es: "es-ES",
|
|
521
|
+
en: "en-GB",
|
|
522
|
+
fr: "fr-FR",
|
|
523
|
+
it: "it-IT",
|
|
524
|
+
de: "de-DE"
|
|
525
|
+
};
|
|
526
|
+
function localeForLang(lang) {
|
|
527
|
+
return isLang(lang) ? LOCALE_BY_LANG[lang] : "es-ES";
|
|
528
|
+
}
|
|
498
529
|
|
|
499
530
|
// src/models/input-types.model.ts
|
|
500
531
|
var INPUT_TYPES = [
|
|
@@ -511,7 +542,7 @@ var INPUT_TYPES = [
|
|
|
511
542
|
// src/models/db-schema.model.ts
|
|
512
543
|
var DB_SCHEMA = {
|
|
513
544
|
name: "E2ECypressDB",
|
|
514
|
-
version:
|
|
545
|
+
version: 11,
|
|
515
546
|
stores: [
|
|
516
547
|
{
|
|
517
548
|
name: "tests",
|
|
@@ -551,11 +582,45 @@ var DB_SCHEMA = {
|
|
|
551
582
|
{ name: "extendedHttpCommands", keyPath: "extendedHttpCommands", unique: false },
|
|
552
583
|
{ name: "allowReadWriteFiles", keyPath: "allowReadWriteFiles", unique: false }
|
|
553
584
|
]
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
// Single-record store (fixed key id=1) holding the live, in-progress
|
|
588
|
+
// recording session so it survives a micro-frontend crossing or reload.
|
|
589
|
+
// See docs/specs/006-cross-app-recording-continuity.md.
|
|
590
|
+
name: "activeSession",
|
|
591
|
+
keyPath: "id",
|
|
592
|
+
autoIncrement: false,
|
|
593
|
+
indexes: []
|
|
554
594
|
}
|
|
555
595
|
]
|
|
556
596
|
};
|
|
557
597
|
var DB_STORE_NAMES = DB_SCHEMA.stores.map((s) => s.name);
|
|
558
598
|
|
|
599
|
+
// src/models/active-session.model.ts
|
|
600
|
+
var ACTIVE_SESSION_BREADCRUMB_KEY = "e2e-active-session";
|
|
601
|
+
var RESUME_TTL_CONFIG_KEY = "resumeRecencyTtlMinutes";
|
|
602
|
+
var DEFAULT_RESUME_TTL_MINUTES = 30;
|
|
603
|
+
|
|
604
|
+
// src/utils/subject.ts
|
|
605
|
+
var Subject = class {
|
|
606
|
+
_value;
|
|
607
|
+
listeners = /* @__PURE__ */ new Set();
|
|
608
|
+
constructor(initialValue) {
|
|
609
|
+
this._value = initialValue;
|
|
610
|
+
}
|
|
611
|
+
next(value) {
|
|
612
|
+
this._value = value;
|
|
613
|
+
this.listeners.forEach((l) => l(value));
|
|
614
|
+
}
|
|
615
|
+
getValue() {
|
|
616
|
+
return this._value;
|
|
617
|
+
}
|
|
618
|
+
subscribe(fn) {
|
|
619
|
+
this.listeners.add(fn);
|
|
620
|
+
return () => this.listeners.delete(fn);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
|
|
559
624
|
// src/i18n/es.ts
|
|
560
625
|
var I18N_ES = {
|
|
561
626
|
MAIN_FRAME: {
|
|
@@ -590,8 +655,6 @@ var I18N_ES = {
|
|
|
590
655
|
COPY_DESCRIBE: "\u{1F5C2} Copiar describe()",
|
|
591
656
|
MULTI_SELECT: "\u{1F5C2} Multi-select",
|
|
592
657
|
CANCEL_SELECT: "\u2715 Cancelar",
|
|
593
|
-
SECTION_COMMANDS: "Comandos",
|
|
594
|
-
SECTION_INTERCEPTORS: "Interceptores",
|
|
595
658
|
COPY_CMDS_BTN: "\u{1F4CB} Copiar comandos",
|
|
596
659
|
COPY_ICPS_BTN: "\u{1F4CB} Copiar interceptores",
|
|
597
660
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
@@ -620,6 +683,18 @@ var I18N_ES = {
|
|
|
620
683
|
DATA_SECTION: "\u{1F4BE} Datos",
|
|
621
684
|
DATA_DESC: "Exporta todos tus tests a JSON o importa una copia de seguridad.",
|
|
622
685
|
EXPORT_BTN: "\u2B06\uFE0F Exportar tests",
|
|
686
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Exportar tests",
|
|
687
|
+
EXPORT_MODE_ALL: "Todo",
|
|
688
|
+
EXPORT_MODE_MANUAL: "Selecci\xF3n manual",
|
|
689
|
+
EXPORT_MODE_TAGS: "Por tags",
|
|
690
|
+
EXPORT_ALL_DESC: "Se exportar\xE1n todos los tests guardados.",
|
|
691
|
+
EXPORT_COUNT: "A exportar:",
|
|
692
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exportar",
|
|
693
|
+
EXPORT_CANCEL: "Cancelar",
|
|
694
|
+
EXPORT_EMPTY: "No hay tests para exportar.",
|
|
695
|
+
EXPORT_NO_TAGS: "No hay tags disponibles.",
|
|
696
|
+
EXPORT_TAGS_HINT: "Selecciona uno o m\xE1s tags para ver qu\xE9 pruebas se exportar\xE1n.",
|
|
697
|
+
EXPORT_RESULT_LABEL: "Pruebas que se exportar\xE1n:",
|
|
623
698
|
IMPORT_BTN: "\u2B07\uFE0F Importar tests",
|
|
624
699
|
FOLDER_UPDATED_TOAST: "\u2713 Carpeta de Cypress actualizada",
|
|
625
700
|
FOLDER_ERROR_TOAST: "Error al acceder a la carpeta",
|
|
@@ -632,7 +707,14 @@ var I18N_ES = {
|
|
|
632
707
|
SMART_SELECTOR_SUB: "Muestra un picker al hacer click en elementos sin selector v\xE1lido.",
|
|
633
708
|
START_HIDDEN_SECTION: "\u{1F441} Visibilidad del widget",
|
|
634
709
|
START_HIDDEN_TITLE: "Iniciar oculto",
|
|
635
|
-
START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo."
|
|
710
|
+
START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo.",
|
|
711
|
+
RESUME_TTL_SECTION: "\u23F1 Continuidad de grabaci\xF3n",
|
|
712
|
+
RESUME_TTL_LABEL: "Reanudar sin preguntar (minutos)",
|
|
713
|
+
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.",
|
|
714
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Posici\xF3n del widget",
|
|
715
|
+
WIDGET_POSITION_HINT: "Arrastra el bot\xF3n de grabar para moverlo donde no estorbe. Se recuerda su posici\xF3n.",
|
|
716
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA Restablecer posici\xF3n",
|
|
717
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida"
|
|
636
718
|
},
|
|
637
719
|
SELECTOR_PICKER: {
|
|
638
720
|
TITLE: "Selecciona un elemento del DOM",
|
|
@@ -673,8 +755,10 @@ var I18N_ES = {
|
|
|
673
755
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Editar manualmente",
|
|
674
756
|
CLOSE_BTN: "\u2715 Cerrar",
|
|
675
757
|
NEW_FILE_BTN: "+ Nuevo archivo",
|
|
758
|
+
NEW_FOLDER_BTN: "+ Nueva carpeta",
|
|
676
759
|
REFRESH_BTN: "\u21BB Actualizar",
|
|
677
760
|
NEW_FILE_PLACEHOLDER: "nombre-del-test",
|
|
761
|
+
NEW_FOLDER_PLACEHOLDER: "nombre-de-carpeta",
|
|
678
762
|
NEW_FILE_CONFIRM: "Crear",
|
|
679
763
|
NEW_FILE_CANCEL: "Cancelar"
|
|
680
764
|
},
|
|
@@ -687,10 +771,19 @@ var I18N_ES = {
|
|
|
687
771
|
NO_CHANGES: "Sin cambios respecto al original",
|
|
688
772
|
NO_CHANGES_SHORT: "Sin cambios",
|
|
689
773
|
LAUNCH_BTN: "\u25B6 Lanzar test",
|
|
774
|
+
LAUNCH_LOCAL_ONLY: "Mu\xE9velo a local para poder probar",
|
|
775
|
+
LAUNCH_RUNNING: "\u23F3 Ejecutando\u2026",
|
|
776
|
+
LAUNCH_PASSED: "\u2713 Prueba superada",
|
|
777
|
+
LAUNCH_FAILED: "\u2717 Prueba fallida",
|
|
778
|
+
LAUNCH_NO_RUNNER: "No se detect\xF3 el runner local (\xBFlo has arrancado?)",
|
|
690
779
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
691
780
|
SAVE_BTN: "\u{1F4BE} Guardar",
|
|
692
781
|
CLOSE_BTN: "\u2715 Cerrar",
|
|
693
|
-
BACK_TO_EDITOR: "\u2190 Volver al editor"
|
|
782
|
+
BACK_TO_EDITOR: "\u2190 Volver al editor",
|
|
783
|
+
INSERT_BTN: "\u{1FA84} Insertar bloques",
|
|
784
|
+
INSERT_TITLE: "Insertar it() y beforeEach() en el contenido del editor",
|
|
785
|
+
INSERT_DONE: "Bloques insertados en el editor",
|
|
786
|
+
INSERT_ERROR: "No se encontr\xF3 un bloque describe() v\xE1lido en el archivo"
|
|
694
787
|
},
|
|
695
788
|
RECORDER: {
|
|
696
789
|
FS_TITLE: "\u{1F4C1} Acceso a ficheros",
|
|
@@ -719,7 +812,12 @@ var I18N_ES = {
|
|
|
719
812
|
BADGE_PAUSED: "\u23F8 PAUSA",
|
|
720
813
|
BADGE_REC: "\u25CF REC",
|
|
721
814
|
STOP_TITLE: "Detener (Ctrl+R)",
|
|
722
|
-
START_TITLE: "Grabar (Ctrl+R)"
|
|
815
|
+
START_TITLE: "Grabar (Ctrl+R)",
|
|
816
|
+
SESSION_RESUME_TITLE: "\u23FA Grabaci\xF3n en curso",
|
|
817
|
+
SESSION_RESUME_TEXT: "Hay una grabaci\xF3n activa sin terminar.",
|
|
818
|
+
SESSION_RESUME_COUNT: "comandos capturados",
|
|
819
|
+
SESSION_CONTINUE_BTN: "Continuar grabando",
|
|
820
|
+
SESSION_DISCARD_BTN: "Descartar"
|
|
723
821
|
}
|
|
724
822
|
};
|
|
725
823
|
|
|
@@ -757,8 +855,6 @@ var I18N_EN = {
|
|
|
757
855
|
COPY_DESCRIBE: "\u{1F5C2} Copy describe()",
|
|
758
856
|
MULTI_SELECT: "\u{1F5C2} Multi-select",
|
|
759
857
|
CANCEL_SELECT: "\u2715 Cancel",
|
|
760
|
-
SECTION_COMMANDS: "Commands",
|
|
761
|
-
SECTION_INTERCEPTORS: "Interceptors",
|
|
762
858
|
COPY_CMDS_BTN: "\u{1F4CB} Copy commands",
|
|
763
859
|
COPY_ICPS_BTN: "\u{1F4CB} Copy interceptors",
|
|
764
860
|
DEFAULT_DESCRIBE: "Test suite",
|
|
@@ -787,6 +883,18 @@ var I18N_EN = {
|
|
|
787
883
|
DATA_SECTION: "\u{1F4BE} Data",
|
|
788
884
|
DATA_DESC: "Export all your tests to JSON or import a backup.",
|
|
789
885
|
EXPORT_BTN: "\u2B06\uFE0F Export tests",
|
|
886
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Export tests",
|
|
887
|
+
EXPORT_MODE_ALL: "All",
|
|
888
|
+
EXPORT_MODE_MANUAL: "Manual selection",
|
|
889
|
+
EXPORT_MODE_TAGS: "By tags",
|
|
890
|
+
EXPORT_ALL_DESC: "All saved tests will be exported.",
|
|
891
|
+
EXPORT_COUNT: "To export:",
|
|
892
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Export",
|
|
893
|
+
EXPORT_CANCEL: "Cancel",
|
|
894
|
+
EXPORT_EMPTY: "No tests to export.",
|
|
895
|
+
EXPORT_NO_TAGS: "No tags available.",
|
|
896
|
+
EXPORT_TAGS_HINT: "Select one or more tags to preview which tests will be exported.",
|
|
897
|
+
EXPORT_RESULT_LABEL: "Tests that will be exported:",
|
|
790
898
|
IMPORT_BTN: "\u2B07\uFE0F Import tests",
|
|
791
899
|
FOLDER_UPDATED_TOAST: "\u2713 Cypress folder updated",
|
|
792
900
|
FOLDER_ERROR_TOAST: "Error accessing the folder",
|
|
@@ -799,7 +907,14 @@ var I18N_EN = {
|
|
|
799
907
|
SMART_SELECTOR_SUB: "Shows a picker when clicking elements with no valid selector.",
|
|
800
908
|
START_HIDDEN_SECTION: "\u{1F441} Widget visibility",
|
|
801
909
|
START_HIDDEN_TITLE: "Start hidden",
|
|
802
|
-
START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it."
|
|
910
|
+
START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it.",
|
|
911
|
+
RESUME_TTL_SECTION: "\u23F1 Recording continuity",
|
|
912
|
+
RESUME_TTL_LABEL: "Resume without asking (minutes)",
|
|
913
|
+
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.",
|
|
914
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Widget position",
|
|
915
|
+
WIDGET_POSITION_HINT: "Drag the record button to move it out of the way. Its position is remembered.",
|
|
916
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA Reset position",
|
|
917
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position reset"
|
|
803
918
|
},
|
|
804
919
|
SELECTOR_PICKER: {
|
|
805
920
|
TITLE: "Select a DOM element",
|
|
@@ -840,8 +955,10 @@ var I18N_EN = {
|
|
|
840
955
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Edit manually",
|
|
841
956
|
CLOSE_BTN: "\u2715 Close",
|
|
842
957
|
NEW_FILE_BTN: "+ New file",
|
|
958
|
+
NEW_FOLDER_BTN: "+ New folder",
|
|
843
959
|
REFRESH_BTN: "\u21BB Refresh",
|
|
844
960
|
NEW_FILE_PLACEHOLDER: "test-name",
|
|
961
|
+
NEW_FOLDER_PLACEHOLDER: "folder-name",
|
|
845
962
|
NEW_FILE_CONFIRM: "Create",
|
|
846
963
|
NEW_FILE_CANCEL: "Cancel"
|
|
847
964
|
},
|
|
@@ -854,10 +971,19 @@ var I18N_EN = {
|
|
|
854
971
|
NO_CHANGES: "No changes from original",
|
|
855
972
|
NO_CHANGES_SHORT: "No changes",
|
|
856
973
|
LAUNCH_BTN: "\u25B6 Run test",
|
|
974
|
+
LAUNCH_LOCAL_ONLY: "Move it to localhost to run tests",
|
|
975
|
+
LAUNCH_RUNNING: "\u23F3 Running\u2026",
|
|
976
|
+
LAUNCH_PASSED: "\u2713 Test passed",
|
|
977
|
+
LAUNCH_FAILED: "\u2717 Test failed",
|
|
978
|
+
LAUNCH_NO_RUNNER: "No local runner detected (did you start it?)",
|
|
857
979
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
858
980
|
SAVE_BTN: "\u{1F4BE} Save",
|
|
859
981
|
CLOSE_BTN: "\u2715 Close",
|
|
860
|
-
BACK_TO_EDITOR: "\u2190 Back to editor"
|
|
982
|
+
BACK_TO_EDITOR: "\u2190 Back to editor",
|
|
983
|
+
INSERT_BTN: "\u{1FA84} Insert blocks",
|
|
984
|
+
INSERT_TITLE: "Insert it() and beforeEach() into the editor content",
|
|
985
|
+
INSERT_DONE: "Blocks inserted into the editor",
|
|
986
|
+
INSERT_ERROR: "No valid describe() block found in the file"
|
|
861
987
|
},
|
|
862
988
|
RECORDER: {
|
|
863
989
|
FS_TITLE: "\u{1F4C1} File access",
|
|
@@ -886,7 +1012,12 @@ var I18N_EN = {
|
|
|
886
1012
|
BADGE_PAUSED: "\u23F8 PAUSED",
|
|
887
1013
|
BADGE_REC: "\u25CF REC",
|
|
888
1014
|
STOP_TITLE: "Stop (Ctrl+R)",
|
|
889
|
-
START_TITLE: "Record (Ctrl+R)"
|
|
1015
|
+
START_TITLE: "Record (Ctrl+R)",
|
|
1016
|
+
SESSION_RESUME_TITLE: "\u23FA Recording in progress",
|
|
1017
|
+
SESSION_RESUME_TEXT: "There is an unfinished active recording.",
|
|
1018
|
+
SESSION_RESUME_COUNT: "commands captured",
|
|
1019
|
+
SESSION_CONTINUE_BTN: "Keep recording",
|
|
1020
|
+
SESSION_DISCARD_BTN: "Discard"
|
|
890
1021
|
}
|
|
891
1022
|
};
|
|
892
1023
|
|
|
@@ -924,8 +1055,6 @@ var I18N_FR = {
|
|
|
924
1055
|
COPY_DESCRIBE: "\u{1F5C2} Copier describe()",
|
|
925
1056
|
MULTI_SELECT: "\u{1F5C2} Multi-s\xE9lection",
|
|
926
1057
|
CANCEL_SELECT: "\u2715 Annuler",
|
|
927
|
-
SECTION_COMMANDS: "Commandes",
|
|
928
|
-
SECTION_INTERCEPTORS: "Intercepteurs",
|
|
929
1058
|
COPY_CMDS_BTN: "\u{1F4CB} Copier les commandes",
|
|
930
1059
|
COPY_ICPS_BTN: "\u{1F4CB} Copier les intercepteurs",
|
|
931
1060
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
@@ -954,6 +1083,18 @@ var I18N_FR = {
|
|
|
954
1083
|
DATA_SECTION: "\u{1F4BE} Donn\xE9es",
|
|
955
1084
|
DATA_DESC: "Exportez tous vos tests en JSON ou importez une sauvegarde.",
|
|
956
1085
|
EXPORT_BTN: "\u2B06\uFE0F Exporter les tests",
|
|
1086
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Exporter les tests",
|
|
1087
|
+
EXPORT_MODE_ALL: "Tout",
|
|
1088
|
+
EXPORT_MODE_MANUAL: "S\xE9lection manuelle",
|
|
1089
|
+
EXPORT_MODE_TAGS: "Par tags",
|
|
1090
|
+
EXPORT_ALL_DESC: "Tous les tests enregistr\xE9s seront export\xE9s.",
|
|
1091
|
+
EXPORT_COUNT: "\xC0 exporter :",
|
|
1092
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exporter",
|
|
1093
|
+
EXPORT_CANCEL: "Annuler",
|
|
1094
|
+
EXPORT_EMPTY: "Aucun test \xE0 exporter.",
|
|
1095
|
+
EXPORT_NO_TAGS: "Aucun tag disponible.",
|
|
1096
|
+
EXPORT_TAGS_HINT: "S\xE9lectionnez un ou plusieurs tags pour voir les tests qui seront export\xE9s.",
|
|
1097
|
+
EXPORT_RESULT_LABEL: "Tests qui seront export\xE9s :",
|
|
957
1098
|
IMPORT_BTN: "\u2B07\uFE0F Importer les tests",
|
|
958
1099
|
FOLDER_UPDATED_TOAST: "\u2713 Dossier Cypress mis \xE0 jour",
|
|
959
1100
|
FOLDER_ERROR_TOAST: "Erreur lors de l'acc\xE8s au dossier",
|
|
@@ -966,7 +1107,14 @@ var I18N_FR = {
|
|
|
966
1107
|
SMART_SELECTOR_SUB: "Affiche un s\xE9lecteur lors du clic sur des \xE9l\xE9ments sans s\xE9lecteur valide.",
|
|
967
1108
|
START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE9 du widget",
|
|
968
1109
|
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."
|
|
1110
|
+
START_HIDDEN_SUB: "Le widget d\xE9marre invisible. Utilisez Ctrl+Shift+E pour l'afficher ou le masquer.",
|
|
1111
|
+
RESUME_TTL_SECTION: "\u23F1 Continuit\xE9 d'enregistrement",
|
|
1112
|
+
RESUME_TTL_LABEL: "Reprendre sans demander (minutes)",
|
|
1113
|
+
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.",
|
|
1114
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Position du widget",
|
|
1115
|
+
WIDGET_POSITION_HINT: "Fais glisser le bouton d'enregistrement pour le d\xE9placer. Sa position est m\xE9moris\xE9e.",
|
|
1116
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA R\xE9initialiser la position",
|
|
1117
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e"
|
|
970
1118
|
},
|
|
971
1119
|
SELECTOR_PICKER: {
|
|
972
1120
|
TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
|
|
@@ -1007,8 +1155,10 @@ var I18N_FR = {
|
|
|
1007
1155
|
EDIT_MANUAL_BTN: "\u270F\uFE0F \xC9diter manuellement",
|
|
1008
1156
|
CLOSE_BTN: "\u2715 Fermer",
|
|
1009
1157
|
NEW_FILE_BTN: "+ Nouveau fichier",
|
|
1158
|
+
NEW_FOLDER_BTN: "+ Nouveau dossier",
|
|
1010
1159
|
REFRESH_BTN: "\u21BB Actualiser",
|
|
1011
1160
|
NEW_FILE_PLACEHOLDER: "nom-du-test",
|
|
1161
|
+
NEW_FOLDER_PLACEHOLDER: "nom-du-dossier",
|
|
1012
1162
|
NEW_FILE_CONFIRM: "Cr\xE9er",
|
|
1013
1163
|
NEW_FILE_CANCEL: "Annuler"
|
|
1014
1164
|
},
|
|
@@ -1021,10 +1171,19 @@ var I18N_FR = {
|
|
|
1021
1171
|
NO_CHANGES: "Aucun changement par rapport \xE0 l'original",
|
|
1022
1172
|
NO_CHANGES_SHORT: "Aucun changement",
|
|
1023
1173
|
LAUNCH_BTN: "\u25B6 Lancer le test",
|
|
1174
|
+
LAUNCH_LOCAL_ONLY: "Passe en local pour pouvoir tester",
|
|
1175
|
+
LAUNCH_RUNNING: "\u23F3 Ex\xE9cution\u2026",
|
|
1176
|
+
LAUNCH_PASSED: "\u2713 Test r\xE9ussi",
|
|
1177
|
+
LAUNCH_FAILED: "\u2717 Test \xE9chou\xE9",
|
|
1178
|
+
LAUNCH_NO_RUNNER: "Aucun runner local d\xE9tect\xE9 (l'avez-vous d\xE9marr\xE9 ?)",
|
|
1024
1179
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1025
1180
|
SAVE_BTN: "\u{1F4BE} Enregistrer",
|
|
1026
1181
|
CLOSE_BTN: "\u2715 Fermer",
|
|
1027
|
-
BACK_TO_EDITOR: "\u2190 Retour \xE0 l'\xE9diteur"
|
|
1182
|
+
BACK_TO_EDITOR: "\u2190 Retour \xE0 l'\xE9diteur",
|
|
1183
|
+
INSERT_BTN: "\u{1FA84} Ins\xE9rer les blocs",
|
|
1184
|
+
INSERT_TITLE: "Ins\xE9rer it() et beforeEach() dans le contenu de l'\xE9diteur",
|
|
1185
|
+
INSERT_DONE: "Blocs ins\xE9r\xE9s dans l'\xE9diteur",
|
|
1186
|
+
INSERT_ERROR: "Aucun bloc describe() valide trouv\xE9 dans le fichier"
|
|
1028
1187
|
},
|
|
1029
1188
|
RECORDER: {
|
|
1030
1189
|
FS_TITLE: "\u{1F4C1} Acc\xE8s aux fichiers",
|
|
@@ -1053,7 +1212,12 @@ var I18N_FR = {
|
|
|
1053
1212
|
BADGE_PAUSED: "\u23F8 PAUSE",
|
|
1054
1213
|
BADGE_REC: "\u25CF REC",
|
|
1055
1214
|
STOP_TITLE: "Arr\xEAter (Ctrl+R)",
|
|
1056
|
-
START_TITLE: "Enregistrer (Ctrl+R)"
|
|
1215
|
+
START_TITLE: "Enregistrer (Ctrl+R)",
|
|
1216
|
+
SESSION_RESUME_TITLE: "\u23FA Enregistrement en cours",
|
|
1217
|
+
SESSION_RESUME_TEXT: "Un enregistrement actif est inachev\xE9.",
|
|
1218
|
+
SESSION_RESUME_COUNT: "commandes captur\xE9es",
|
|
1219
|
+
SESSION_CONTINUE_BTN: "Continuer l'enregistrement",
|
|
1220
|
+
SESSION_DISCARD_BTN: "Ignorer"
|
|
1057
1221
|
}
|
|
1058
1222
|
};
|
|
1059
1223
|
|
|
@@ -1091,8 +1255,6 @@ var I18N_IT = {
|
|
|
1091
1255
|
COPY_DESCRIBE: "\u{1F5C2} Copia describe()",
|
|
1092
1256
|
MULTI_SELECT: "\u{1F5C2} Multi-selezione",
|
|
1093
1257
|
CANCEL_SELECT: "\u2715 Annulla",
|
|
1094
|
-
SECTION_COMMANDS: "Comandi",
|
|
1095
|
-
SECTION_INTERCEPTORS: "Interceptor",
|
|
1096
1258
|
COPY_CMDS_BTN: "\u{1F4CB} Copia comandi",
|
|
1097
1259
|
COPY_ICPS_BTN: "\u{1F4CB} Copia interceptor",
|
|
1098
1260
|
DEFAULT_DESCRIBE: "Suite di test",
|
|
@@ -1121,6 +1283,18 @@ var I18N_IT = {
|
|
|
1121
1283
|
DATA_SECTION: "\u{1F4BE} Dati",
|
|
1122
1284
|
DATA_DESC: "Esporta tutti i tuoi test in JSON o importa un backup.",
|
|
1123
1285
|
EXPORT_BTN: "\u2B06\uFE0F Esporta test",
|
|
1286
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Esporta test",
|
|
1287
|
+
EXPORT_MODE_ALL: "Tutto",
|
|
1288
|
+
EXPORT_MODE_MANUAL: "Selezione manuale",
|
|
1289
|
+
EXPORT_MODE_TAGS: "Per tag",
|
|
1290
|
+
EXPORT_ALL_DESC: "Verranno esportati tutti i test salvati.",
|
|
1291
|
+
EXPORT_COUNT: "Da esportare:",
|
|
1292
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Esporta",
|
|
1293
|
+
EXPORT_CANCEL: "Annulla",
|
|
1294
|
+
EXPORT_EMPTY: "Nessun test da esportare.",
|
|
1295
|
+
EXPORT_NO_TAGS: "Nessun tag disponibile.",
|
|
1296
|
+
EXPORT_TAGS_HINT: "Seleziona uno o pi\xF9 tag per vedere quali test verranno esportati.",
|
|
1297
|
+
EXPORT_RESULT_LABEL: "Test che verranno esportati:",
|
|
1124
1298
|
IMPORT_BTN: "\u2B07\uFE0F Importa test",
|
|
1125
1299
|
FOLDER_UPDATED_TOAST: "\u2713 Cartella Cypress aggiornata",
|
|
1126
1300
|
FOLDER_ERROR_TOAST: "Errore durante l'accesso alla cartella",
|
|
@@ -1133,7 +1307,14 @@ var I18N_IT = {
|
|
|
1133
1307
|
SMART_SELECTOR_SUB: "Mostra un selettore quando si fa clic su elementi senza selettore valido.",
|
|
1134
1308
|
START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE0 del widget",
|
|
1135
1309
|
START_HIDDEN_TITLE: "Avvia nascosto",
|
|
1136
|
-
START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo."
|
|
1310
|
+
START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo.",
|
|
1311
|
+
RESUME_TTL_SECTION: "\u23F1 Continuit\xE0 di registrazione",
|
|
1312
|
+
RESUME_TTL_LABEL: "Riprendi senza chiedere (minuti)",
|
|
1313
|
+
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.",
|
|
1314
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Posizione del widget",
|
|
1315
|
+
WIDGET_POSITION_HINT: "Trascina il pulsante di registrazione per spostarlo. La posizione viene ricordata.",
|
|
1316
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA Reimposta posizione",
|
|
1317
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata"
|
|
1137
1318
|
},
|
|
1138
1319
|
SELECTOR_PICKER: {
|
|
1139
1320
|
TITLE: "Seleziona un elemento DOM",
|
|
@@ -1174,8 +1355,10 @@ var I18N_IT = {
|
|
|
1174
1355
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Modifica manualmente",
|
|
1175
1356
|
CLOSE_BTN: "\u2715 Chiudi",
|
|
1176
1357
|
NEW_FILE_BTN: "+ Nuovo file",
|
|
1358
|
+
NEW_FOLDER_BTN: "+ Nuova cartella",
|
|
1177
1359
|
REFRESH_BTN: "\u21BB Aggiorna",
|
|
1178
1360
|
NEW_FILE_PLACEHOLDER: "nome-del-test",
|
|
1361
|
+
NEW_FOLDER_PLACEHOLDER: "nome-cartella",
|
|
1179
1362
|
NEW_FILE_CONFIRM: "Crea",
|
|
1180
1363
|
NEW_FILE_CANCEL: "Annulla"
|
|
1181
1364
|
},
|
|
@@ -1188,10 +1371,19 @@ var I18N_IT = {
|
|
|
1188
1371
|
NO_CHANGES: "Nessuna modifica rispetto all'originale",
|
|
1189
1372
|
NO_CHANGES_SHORT: "Nessuna modifica",
|
|
1190
1373
|
LAUNCH_BTN: "\u25B6 Esegui test",
|
|
1374
|
+
LAUNCH_LOCAL_ONLY: "Spostati in locale per poter testare",
|
|
1375
|
+
LAUNCH_RUNNING: "\u23F3 Esecuzione\u2026",
|
|
1376
|
+
LAUNCH_PASSED: "\u2713 Test superato",
|
|
1377
|
+
LAUNCH_FAILED: "\u2717 Test fallito",
|
|
1378
|
+
LAUNCH_NO_RUNNER: "Nessun runner locale rilevato (l'hai avviato?)",
|
|
1191
1379
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1192
1380
|
SAVE_BTN: "\u{1F4BE} Salva",
|
|
1193
1381
|
CLOSE_BTN: "\u2715 Chiudi",
|
|
1194
|
-
BACK_TO_EDITOR: "\u2190 Torna all'editor"
|
|
1382
|
+
BACK_TO_EDITOR: "\u2190 Torna all'editor",
|
|
1383
|
+
INSERT_BTN: "\u{1FA84} Inserisci blocchi",
|
|
1384
|
+
INSERT_TITLE: "Inserisci it() e beforeEach() nel contenuto dell'editor",
|
|
1385
|
+
INSERT_DONE: "Blocchi inseriti nell'editor",
|
|
1386
|
+
INSERT_ERROR: "Nessun blocco describe() valido trovato nel file"
|
|
1195
1387
|
},
|
|
1196
1388
|
RECORDER: {
|
|
1197
1389
|
FS_TITLE: "\u{1F4C1} Accesso ai file",
|
|
@@ -1220,7 +1412,12 @@ var I18N_IT = {
|
|
|
1220
1412
|
BADGE_PAUSED: "\u23F8 PAUSA",
|
|
1221
1413
|
BADGE_REC: "\u25CF REC",
|
|
1222
1414
|
STOP_TITLE: "Ferma (Ctrl+R)",
|
|
1223
|
-
START_TITLE: "Registra (Ctrl+R)"
|
|
1415
|
+
START_TITLE: "Registra (Ctrl+R)",
|
|
1416
|
+
SESSION_RESUME_TITLE: "\u23FA Registrazione in corso",
|
|
1417
|
+
SESSION_RESUME_TEXT: "C'\xE8 una registrazione attiva non terminata.",
|
|
1418
|
+
SESSION_RESUME_COUNT: "comandi catturati",
|
|
1419
|
+
SESSION_CONTINUE_BTN: "Continua a registrare",
|
|
1420
|
+
SESSION_DISCARD_BTN: "Annulla"
|
|
1224
1421
|
}
|
|
1225
1422
|
};
|
|
1226
1423
|
|
|
@@ -1258,8 +1455,6 @@ var I18N_DE = {
|
|
|
1258
1455
|
COPY_DESCRIBE: "\u{1F5C2} describe() kopieren",
|
|
1259
1456
|
MULTI_SELECT: "\u{1F5C2} Mehrfachauswahl",
|
|
1260
1457
|
CANCEL_SELECT: "\u2715 Abbrechen",
|
|
1261
|
-
SECTION_COMMANDS: "Befehle",
|
|
1262
|
-
SECTION_INTERCEPTORS: "Interceptors",
|
|
1263
1458
|
COPY_CMDS_BTN: "\u{1F4CB} Befehle kopieren",
|
|
1264
1459
|
COPY_ICPS_BTN: "\u{1F4CB} Interceptors kopieren",
|
|
1265
1460
|
DEFAULT_DESCRIBE: "Test-Suite",
|
|
@@ -1288,6 +1483,18 @@ var I18N_DE = {
|
|
|
1288
1483
|
DATA_SECTION: "\u{1F4BE} Daten",
|
|
1289
1484
|
DATA_DESC: "Exportieren Sie alle Tests als JSON oder importieren Sie ein Backup.",
|
|
1290
1485
|
EXPORT_BTN: "\u2B06\uFE0F Tests exportieren",
|
|
1486
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Tests exportieren",
|
|
1487
|
+
EXPORT_MODE_ALL: "Alle",
|
|
1488
|
+
EXPORT_MODE_MANUAL: "Manuelle Auswahl",
|
|
1489
|
+
EXPORT_MODE_TAGS: "Nach Tags",
|
|
1490
|
+
EXPORT_ALL_DESC: "Alle gespeicherten Tests werden exportiert.",
|
|
1491
|
+
EXPORT_COUNT: "Zu exportieren:",
|
|
1492
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exportieren",
|
|
1493
|
+
EXPORT_CANCEL: "Abbrechen",
|
|
1494
|
+
EXPORT_EMPTY: "Keine Tests zum Exportieren.",
|
|
1495
|
+
EXPORT_NO_TAGS: "Keine Tags verf\xFCgbar.",
|
|
1496
|
+
EXPORT_TAGS_HINT: "W\xE4hle ein oder mehrere Tags, um zu sehen, welche Tests exportiert werden.",
|
|
1497
|
+
EXPORT_RESULT_LABEL: "Diese Tests werden exportiert:",
|
|
1291
1498
|
IMPORT_BTN: "\u2B07\uFE0F Tests importieren",
|
|
1292
1499
|
FOLDER_UPDATED_TOAST: "\u2713 Cypress-Ordner aktualisiert",
|
|
1293
1500
|
FOLDER_ERROR_TOAST: "Fehler beim Zugriff auf den Ordner",
|
|
@@ -1300,7 +1507,14 @@ var I18N_DE = {
|
|
|
1300
1507
|
SMART_SELECTOR_SUB: "Zeigt ein Auswahlfeld, wenn auf Elemente ohne g\xFCltigen Selektor geklickt wird.",
|
|
1301
1508
|
START_HIDDEN_SECTION: "\u{1F441} Widget-Sichtbarkeit",
|
|
1302
1509
|
START_HIDDEN_TITLE: "Versteckt starten",
|
|
1303
|
-
START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden."
|
|
1510
|
+
START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden.",
|
|
1511
|
+
RESUME_TTL_SECTION: "\u23F1 Aufzeichnungskontinuit\xE4t",
|
|
1512
|
+
RESUME_TTL_LABEL: "Ohne Nachfrage fortsetzen (Minuten)",
|
|
1513
|
+
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.",
|
|
1514
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Widget-Position",
|
|
1515
|
+
WIDGET_POSITION_HINT: "Ziehe den Aufnahme-Button, um ihn aus dem Weg zu schieben. Die Position wird gespeichert.",
|
|
1516
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA Position zur\xFCcksetzen",
|
|
1517
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt"
|
|
1304
1518
|
},
|
|
1305
1519
|
SELECTOR_PICKER: {
|
|
1306
1520
|
TITLE: "DOM-Element ausw\xE4hlen",
|
|
@@ -1341,8 +1555,10 @@ var I18N_DE = {
|
|
|
1341
1555
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Manuell bearbeiten",
|
|
1342
1556
|
CLOSE_BTN: "\u2715 Schlie\xDFen",
|
|
1343
1557
|
NEW_FILE_BTN: "+ Neue Datei",
|
|
1558
|
+
NEW_FOLDER_BTN: "+ Neuer Ordner",
|
|
1344
1559
|
REFRESH_BTN: "\u21BB Aktualisieren",
|
|
1345
1560
|
NEW_FILE_PLACEHOLDER: "test-name",
|
|
1561
|
+
NEW_FOLDER_PLACEHOLDER: "ordnername",
|
|
1346
1562
|
NEW_FILE_CONFIRM: "Erstellen",
|
|
1347
1563
|
NEW_FILE_CANCEL: "Abbrechen"
|
|
1348
1564
|
},
|
|
@@ -1355,10 +1571,19 @@ var I18N_DE = {
|
|
|
1355
1571
|
NO_CHANGES: "Keine \xC4nderungen gegen\xFCber dem Original",
|
|
1356
1572
|
NO_CHANGES_SHORT: "Keine \xC4nderungen",
|
|
1357
1573
|
LAUNCH_BTN: "\u25B6 Test starten",
|
|
1574
|
+
LAUNCH_LOCAL_ONLY: "Wechsle zu localhost, um Tests auszuf\xFChren",
|
|
1575
|
+
LAUNCH_RUNNING: "\u23F3 L\xE4uft\u2026",
|
|
1576
|
+
LAUNCH_PASSED: "\u2713 Test bestanden",
|
|
1577
|
+
LAUNCH_FAILED: "\u2717 Test fehlgeschlagen",
|
|
1578
|
+
LAUNCH_NO_RUNNER: "Kein lokaler Runner erkannt (gestartet?)",
|
|
1358
1579
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
1359
1580
|
SAVE_BTN: "\u{1F4BE} Speichern",
|
|
1360
1581
|
CLOSE_BTN: "\u2715 Schlie\xDFen",
|
|
1361
|
-
BACK_TO_EDITOR: "\u2190 Zur\xFCck zum Editor"
|
|
1582
|
+
BACK_TO_EDITOR: "\u2190 Zur\xFCck zum Editor",
|
|
1583
|
+
INSERT_BTN: "\u{1FA84} Bl\xF6cke einf\xFCgen",
|
|
1584
|
+
INSERT_TITLE: "it() und beforeEach() in den Editor-Inhalt einf\xFCgen",
|
|
1585
|
+
INSERT_DONE: "Bl\xF6cke in den Editor eingef\xFCgt",
|
|
1586
|
+
INSERT_ERROR: "Kein g\xFCltiger describe()-Block in der Datei gefunden"
|
|
1362
1587
|
},
|
|
1363
1588
|
RECORDER: {
|
|
1364
1589
|
FS_TITLE: "\u{1F4C1} Dateizugriff",
|
|
@@ -1387,13 +1612,18 @@ var I18N_DE = {
|
|
|
1387
1612
|
BADGE_PAUSED: "\u23F8 PAUSE",
|
|
1388
1613
|
BADGE_REC: "\u25CF REC",
|
|
1389
1614
|
STOP_TITLE: "Stopp (Ctrl+R)",
|
|
1390
|
-
START_TITLE: "Aufzeichnen (Ctrl+R)"
|
|
1615
|
+
START_TITLE: "Aufzeichnen (Ctrl+R)",
|
|
1616
|
+
SESSION_RESUME_TITLE: "\u23FA Aufzeichnung l\xE4uft",
|
|
1617
|
+
SESSION_RESUME_TEXT: "Es gibt eine nicht abgeschlossene aktive Aufzeichnung.",
|
|
1618
|
+
SESSION_RESUME_COUNT: "erfasste Befehle",
|
|
1619
|
+
SESSION_CONTINUE_BTN: "Weiter aufzeichnen",
|
|
1620
|
+
SESSION_DISCARD_BTN: "Verwerfen"
|
|
1391
1621
|
}
|
|
1392
1622
|
};
|
|
1393
1623
|
|
|
1394
1624
|
// src/services/translation.service.ts
|
|
1395
1625
|
var TranslationService = class {
|
|
1396
|
-
lang
|
|
1626
|
+
lang$;
|
|
1397
1627
|
translations = {
|
|
1398
1628
|
es: I18N_ES,
|
|
1399
1629
|
en: I18N_EN,
|
|
@@ -1402,17 +1632,21 @@ var TranslationService = class {
|
|
|
1402
1632
|
de: I18N_DE
|
|
1403
1633
|
};
|
|
1404
1634
|
constructor() {
|
|
1405
|
-
this.lang = this.detectLang();
|
|
1635
|
+
this.lang$ = new Subject(this.detectLang());
|
|
1406
1636
|
}
|
|
1407
1637
|
setLang(lang) {
|
|
1408
|
-
this.lang
|
|
1638
|
+
this.lang$.next(lang);
|
|
1409
1639
|
}
|
|
1410
1640
|
getLang() {
|
|
1411
|
-
return this.lang;
|
|
1641
|
+
return this.lang$.getValue();
|
|
1642
|
+
}
|
|
1643
|
+
/** Subscribe to language changes; returns an unsubscribe function. */
|
|
1644
|
+
onLangChange(fn) {
|
|
1645
|
+
return this.lang$.subscribe(fn);
|
|
1412
1646
|
}
|
|
1413
1647
|
translate(key) {
|
|
1414
1648
|
const keys = key.split(".");
|
|
1415
|
-
let value = this.translations[this.lang];
|
|
1649
|
+
let value = this.translations[this.lang$.getValue()];
|
|
1416
1650
|
for (const k of keys) {
|
|
1417
1651
|
value = value?.[k];
|
|
1418
1652
|
if (value === void 0) return key;
|
|
@@ -1435,6 +1669,9 @@ function gcd(a, b) {
|
|
|
1435
1669
|
}
|
|
1436
1670
|
return a;
|
|
1437
1671
|
}
|
|
1672
|
+
function escapeSingleQuotes(value) {
|
|
1673
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1674
|
+
}
|
|
1438
1675
|
function normalizeBlock(code, baseIndent) {
|
|
1439
1676
|
const lines = code.split("\n").map((l) => l.trimEnd());
|
|
1440
1677
|
const nonEmpty = lines.filter((l) => l.trim().length > 0);
|
|
@@ -1457,7 +1694,7 @@ var TransformationService = class {
|
|
|
1457
1694
|
}
|
|
1458
1695
|
generateItDescription(description, commands) {
|
|
1459
1696
|
const commandsBlock = commands.map((cmd) => normalizeBlock(cmd, " ")).join("\n");
|
|
1460
|
-
return `it('${description}', () => {
|
|
1697
|
+
return `it('${escapeSingleQuotes(description)}', () => {
|
|
1461
1698
|
${commandsBlock}
|
|
1462
1699
|
});`;
|
|
1463
1700
|
}
|
|
@@ -1511,29 +1748,15 @@ ${lines}
|
|
|
1511
1748
|
};
|
|
1512
1749
|
var advancedTestTransformationService = new AdvancedTestTransformationService();
|
|
1513
1750
|
|
|
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
1751
|
// src/services/recording.service.ts
|
|
1535
1752
|
init_selector_quality_utils();
|
|
1536
1753
|
var OWN_SELECTOR = '[data-cy="lib-e2e-cypress-for-dummys"]';
|
|
1754
|
+
function createSessionId() {
|
|
1755
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1756
|
+
return crypto.randomUUID();
|
|
1757
|
+
}
|
|
1758
|
+
return `sess-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;
|
|
1759
|
+
}
|
|
1537
1760
|
var RecordingService = class {
|
|
1538
1761
|
commands$ = new Subject([]);
|
|
1539
1762
|
interceptors$ = new Subject([]);
|
|
@@ -1543,6 +1766,9 @@ var RecordingService = class {
|
|
|
1543
1766
|
inputDebounceTimers = /* @__PURE__ */ new Map();
|
|
1544
1767
|
abort = new AbortController();
|
|
1545
1768
|
selectorStrategy = "data-cy";
|
|
1769
|
+
/** Stable id of the current live session (null when none has started). */
|
|
1770
|
+
sessionId = null;
|
|
1771
|
+
startedAt = 0;
|
|
1546
1772
|
// Stored originals for history patching cleanup
|
|
1547
1773
|
origPushState = history.pushState.bind(history);
|
|
1548
1774
|
origReplaceState = history.replaceState.bind(history);
|
|
@@ -1554,12 +1780,42 @@ var RecordingService = class {
|
|
|
1554
1780
|
}
|
|
1555
1781
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
1556
1782
|
startRecording() {
|
|
1783
|
+
this.sessionId = createSessionId();
|
|
1784
|
+
this.startedAt = Date.now();
|
|
1557
1785
|
this.isPaused$.next(false);
|
|
1558
1786
|
this.isRecording$.next(true);
|
|
1559
1787
|
this.addCommand(`cy.viewport(1900, 1200)`);
|
|
1560
1788
|
this.addCommand(`cy.visit('${window.location.pathname}')`);
|
|
1561
1789
|
this.addCommand(`cy.get('[data-cy="lib-e2e-cypress-for-dummys"]').invoke('hide');`);
|
|
1562
1790
|
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Rehydrates a previously persisted session WITHOUT running the startRecording
|
|
1793
|
+
* bootstrap (no viewport/visit/hide). Used to continue a recording across a
|
|
1794
|
+
* micro-frontend crossing or a same-origin reload.
|
|
1795
|
+
* See docs/specs/006-cross-app-recording-continuity.md.
|
|
1796
|
+
*/
|
|
1797
|
+
restoreSession(state) {
|
|
1798
|
+
this.sessionId = state.sessionId;
|
|
1799
|
+
this.startedAt = state.startedAt;
|
|
1800
|
+
this.selectorStrategy = state.selectorStrategy;
|
|
1801
|
+
this.commands$.next([...state.commands]);
|
|
1802
|
+
this.interceptors$.next([...state.interceptors]);
|
|
1803
|
+
this.isPaused$.next(state.isPaused);
|
|
1804
|
+
this.isRecording$.next(state.isRecording);
|
|
1805
|
+
}
|
|
1806
|
+
/** Full snapshot of the live session for persistence. */
|
|
1807
|
+
getSessionSnapshot() {
|
|
1808
|
+
return {
|
|
1809
|
+
sessionId: this.sessionId ?? createSessionId(),
|
|
1810
|
+
isRecording: this.isRecording$.getValue(),
|
|
1811
|
+
isPaused: this.isPaused$.getValue(),
|
|
1812
|
+
commands: this.commands$.getValue(),
|
|
1813
|
+
interceptors: this.interceptors$.getValue(),
|
|
1814
|
+
selectorStrategy: this.selectorStrategy,
|
|
1815
|
+
startedAt: this.startedAt,
|
|
1816
|
+
updatedAt: Date.now()
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1563
1819
|
stopRecording() {
|
|
1564
1820
|
this.isPaused$.next(false);
|
|
1565
1821
|
this.isRecording$.next(false);
|
|
@@ -1646,6 +1902,21 @@ var RecordingService = class {
|
|
|
1646
1902
|
onPauseChange(fn) {
|
|
1647
1903
|
return this.isPaused$.subscribe(fn);
|
|
1648
1904
|
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Fires a full session snapshot whenever any persisted field changes
|
|
1907
|
+
* (commands, interceptors, recording or paused state). Drives the debounced
|
|
1908
|
+
* persistence of the live session. Returns a combined unsubscribe.
|
|
1909
|
+
*/
|
|
1910
|
+
onSessionChange(fn) {
|
|
1911
|
+
const emit = () => fn(this.getSessionSnapshot());
|
|
1912
|
+
const unsubs = [
|
|
1913
|
+
this.commands$.subscribe(emit),
|
|
1914
|
+
this.interceptors$.subscribe(emit),
|
|
1915
|
+
this.isRecording$.subscribe(emit),
|
|
1916
|
+
this.isPaused$.subscribe(emit)
|
|
1917
|
+
];
|
|
1918
|
+
return () => unsubs.forEach((u) => u());
|
|
1919
|
+
}
|
|
1649
1920
|
onSelectorNotFound(fn) {
|
|
1650
1921
|
return this.selectorNotFound$.subscribe((v) => {
|
|
1651
1922
|
if (v) fn(v.target, v.action);
|
|
@@ -1877,6 +2148,7 @@ var RecordingService = class {
|
|
|
1877
2148
|
|
|
1878
2149
|
// src/services/persistence.service.ts
|
|
1879
2150
|
var import_idb = require("idb");
|
|
2151
|
+
var ACTIVE_SESSION_ID = 1;
|
|
1880
2152
|
var PersistenceService = class {
|
|
1881
2153
|
// dbName is overridable so tests can use unique names for isolation.
|
|
1882
2154
|
constructor(dbName = DB_SCHEMA.name) {
|
|
@@ -1930,7 +2202,7 @@ var PersistenceService = class {
|
|
|
1930
2202
|
if (!record) return null;
|
|
1931
2203
|
const commands = await this.getCommandStrings(testId);
|
|
1932
2204
|
const interceptors = await this.getInterceptorStrings(testId);
|
|
1933
|
-
const itBlock = `it('${record.name}', () => {
|
|
2205
|
+
const itBlock = `it('${escapeSingleQuotes(record.name)}', () => {
|
|
1934
2206
|
${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
1935
2207
|
});`;
|
|
1936
2208
|
const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
|
|
@@ -2011,6 +2283,25 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
2011
2283
|
const records = await db.getAll("configuration");
|
|
2012
2284
|
return records[0] ?? null;
|
|
2013
2285
|
}
|
|
2286
|
+
// ── Active recording session ──────────────────────────────────────────────
|
|
2287
|
+
/** Upserts the live recording session (single record, fixed key). */
|
|
2288
|
+
async saveActiveSession(state) {
|
|
2289
|
+
const db = await this.getDB();
|
|
2290
|
+
await db.put("activeSession", { ...state, id: ACTIVE_SESSION_ID });
|
|
2291
|
+
}
|
|
2292
|
+
/** Returns the persisted live session, or null when none is stored. */
|
|
2293
|
+
async getActiveSession() {
|
|
2294
|
+
const db = await this.getDB();
|
|
2295
|
+
const record = await db.get("activeSession", ACTIVE_SESSION_ID);
|
|
2296
|
+
if (!record) return null;
|
|
2297
|
+
const { id: _id, ...state } = record;
|
|
2298
|
+
return state;
|
|
2299
|
+
}
|
|
2300
|
+
/** Removes the live session record. Safe to call when none exists. */
|
|
2301
|
+
async clearActiveSession() {
|
|
2302
|
+
const db = await this.getDB();
|
|
2303
|
+
await db.delete("activeSession", ACTIVE_SESSION_ID);
|
|
2304
|
+
}
|
|
2014
2305
|
// ── Bulk operations ───────────────────────────────────────────────────────
|
|
2015
2306
|
async clearAllData() {
|
|
2016
2307
|
const db = await this.getDB();
|
|
@@ -2021,10 +2312,18 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
2021
2312
|
]);
|
|
2022
2313
|
}
|
|
2023
2314
|
async ingestFileData(tests, interceptors) {
|
|
2024
|
-
|
|
2025
|
-
this.
|
|
2026
|
-
|
|
2027
|
-
|
|
2315
|
+
if (Array.isArray(tests)) {
|
|
2316
|
+
const db = await this.getDB();
|
|
2317
|
+
for (const test of tests) {
|
|
2318
|
+
const { id: _id, commands, interceptors: testInterceptors, ...record } = test;
|
|
2319
|
+
const newId = await db.add("tests", record);
|
|
2320
|
+
const cmds = Array.isArray(commands) ? commands : [];
|
|
2321
|
+
const icps = Array.isArray(testInterceptors) ? testInterceptors : [];
|
|
2322
|
+
if (cmds.length) await this.insertCommands(cmds, newId);
|
|
2323
|
+
if (icps.length) await this.insertInterceptors(icps, newId);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
await this.bulkInsertWithoutId("interceptors", interceptors);
|
|
2028
2327
|
}
|
|
2029
2328
|
async requestDirectoryPermissions() {
|
|
2030
2329
|
if (!("showDirectoryPicker" in window)) {
|
|
@@ -2467,6 +2766,59 @@ function showToast(message, isSuccess = true) {
|
|
|
2467
2766
|
setTimeout(() => toast.remove(), 3e3);
|
|
2468
2767
|
}
|
|
2469
2768
|
|
|
2769
|
+
// src/utils/export-selection.utils.ts
|
|
2770
|
+
function selectTestsForExport(tests, mode, opts = {}) {
|
|
2771
|
+
if (mode === "all") return [...tests];
|
|
2772
|
+
if (mode === "manual") {
|
|
2773
|
+
const ids = new Set(opts.ids ?? []);
|
|
2774
|
+
return tests.filter((t) => ids.has(t.id));
|
|
2775
|
+
}
|
|
2776
|
+
const tags = new Set(opts.tags ?? []);
|
|
2777
|
+
if (tags.size === 0) return [];
|
|
2778
|
+
return tests.filter((t) => (t.tags ?? []).some((tag) => tags.has(tag)));
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// src/utils/host.utils.ts
|
|
2782
|
+
function isLocalHost(hostname) {
|
|
2783
|
+
if (!hostname) return true;
|
|
2784
|
+
const h = hostname.toLowerCase();
|
|
2785
|
+
return h === "localhost" || h.endsWith(".localhost") || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0";
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
// src/utils/widget-position.utils.ts
|
|
2789
|
+
var TOGGLE_MARGIN = 30;
|
|
2790
|
+
var DRAG_THRESHOLD = 5;
|
|
2791
|
+
var DEFAULT_EDGE_OFFSET = 46;
|
|
2792
|
+
function boxOffsetForDirection(dir) {
|
|
2793
|
+
return {
|
|
2794
|
+
x: dir.endsWith("left") ? 144 : 46,
|
|
2795
|
+
y: dir.startsWith("up") ? 144 : 46
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
function clampTogglePosition(x, y, vw, vh, margin = TOGGLE_MARGIN) {
|
|
2799
|
+
const maxX = Math.max(margin, vw - margin);
|
|
2800
|
+
const maxY = Math.max(margin, vh - margin);
|
|
2801
|
+
return {
|
|
2802
|
+
x: Math.min(Math.max(x, margin), maxX),
|
|
2803
|
+
y: Math.min(Math.max(y, margin), maxY)
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
function resolveExpandDirection(x, y, vw, vh) {
|
|
2807
|
+
const vertical = y < vh / 2 ? "down" : "up";
|
|
2808
|
+
const horizontal = x < vw / 2 ? "right" : "left";
|
|
2809
|
+
return `${vertical}-${horizontal}`;
|
|
2810
|
+
}
|
|
2811
|
+
function defaultTogglePosition(vw, vh) {
|
|
2812
|
+
return { x: vw - DEFAULT_EDGE_OFFSET, y: vh - DEFAULT_EDGE_OFFSET };
|
|
2813
|
+
}
|
|
2814
|
+
function boxTopLeftFor(toggleCentre, dir) {
|
|
2815
|
+
const off = boxOffsetForDirection(dir);
|
|
2816
|
+
return { x: toggleCentre.x - off.x, y: toggleCentre.y - off.y };
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// src/index.ts
|
|
2820
|
+
init_selector_picker();
|
|
2821
|
+
|
|
2470
2822
|
// src/components/test-previsualizer/test-previsualizer.styles.ts
|
|
2471
2823
|
var TEST_PREVISUALIZER_STYLES = `
|
|
2472
2824
|
: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 +3421,7 @@ function highlightLine(line) {
|
|
|
3069
3421
|
|
|
3070
3422
|
// src/components/test-editor/test-editor.template.ts
|
|
3071
3423
|
function renderTestEditor(state, t) {
|
|
3072
|
-
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest } = state;
|
|
3424
|
+
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest, locale } = state;
|
|
3073
3425
|
const tagFilterHtml = tags.length ? `<div class="tag-filter">
|
|
3074
3426
|
${tags.map((tag) => `<button class="tag-chip${activeTag === tag ? " active" : ""}" data-filter-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}
|
|
3075
3427
|
</div>` : `<div class="tag-filter" style="color:#484f58;font-size:11px">${t("TEST_EDITOR.NO_TAGS")}</div>`;
|
|
@@ -3080,12 +3432,12 @@ function renderTestEditor(state, t) {
|
|
|
3080
3432
|
</div>` : "";
|
|
3081
3433
|
const rows = visible.map((test, i) => {
|
|
3082
3434
|
const expanded = expandedIndex === i;
|
|
3083
|
-
const date = new Date(test.createdAt).toLocaleString(
|
|
3435
|
+
const date = new Date(test.createdAt).toLocaleString(locale, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
3084
3436
|
const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
|
|
3085
3437
|
const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
|
|
3086
3438
|
const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
|
|
3087
3439
|
const hasIcps = (test.interceptors ?? []).length > 0;
|
|
3088
|
-
const itBlockCode = `it('${test.name}', () => {
|
|
3440
|
+
const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
|
|
3089
3441
|
${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
3090
3442
|
});`;
|
|
3091
3443
|
const icpBlockCode = icps.length ? `beforeEach(() => {
|
|
@@ -3193,11 +3545,11 @@ var TestEditorElement = class extends HTMLElement {
|
|
|
3193
3545
|
` : "";
|
|
3194
3546
|
const itBlocks = selected.map((t) => {
|
|
3195
3547
|
const cmds = (t.commands ?? []).map((c) => ` ${c}`).join("\n");
|
|
3196
|
-
return ` it('${(t.name ?? "")
|
|
3548
|
+
return ` it('${escapeSingleQuotes(t.name ?? "")}', () => {
|
|
3197
3549
|
${cmds}
|
|
3198
3550
|
});`;
|
|
3199
3551
|
}).join("\n\n");
|
|
3200
|
-
const block = `describe('${name
|
|
3552
|
+
const block = `describe('${escapeSingleQuotes(name)}', () => {
|
|
3201
3553
|
${beforeEach}${itBlocks}
|
|
3202
3554
|
});`;
|
|
3203
3555
|
navigator.clipboard?.writeText(block);
|
|
@@ -3231,7 +3583,8 @@ ${beforeEach}${itBlocks}
|
|
|
3231
3583
|
selectedIds: this.selectedIds,
|
|
3232
3584
|
describeName: this.describeName,
|
|
3233
3585
|
expandedIndex: this.expandedIndex,
|
|
3234
|
-
interceptorsByTest: this.interceptorsByTest
|
|
3586
|
+
interceptorsByTest: this.interceptorsByTest,
|
|
3587
|
+
locale: localeForLang(this.translation.getLang())
|
|
3235
3588
|
}, this.t.bind(this))}`;
|
|
3236
3589
|
this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
|
|
3237
3590
|
this.shadow.querySelectorAll("[data-filter-tag]").forEach((el) => {
|
|
@@ -3388,9 +3741,75 @@ var CONFIGURATION_STYLES = `
|
|
|
3388
3741
|
|
|
3389
3742
|
/* \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
3743
|
.data-desc { font-size: 11px; color: #484f58; margin-bottom: 10px; line-height: 1.5; }
|
|
3744
|
+
|
|
3745
|
+
/* \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 */
|
|
3746
|
+
.export-overlay {
|
|
3747
|
+
position: fixed; inset: 0; z-index: 100000;
|
|
3748
|
+
background: rgba(1,4,9,0.7);
|
|
3749
|
+
display: flex; align-items: center; justify-content: center; padding: 20px;
|
|
3750
|
+
}
|
|
3751
|
+
.export-modal {
|
|
3752
|
+
width: 640px; max-width: 100%; max-height: 86vh;
|
|
3753
|
+
display: flex; flex-direction: column;
|
|
3754
|
+
background: #161b22; border: 1px solid #30363d; border-radius: 12px;
|
|
3755
|
+
box-shadow: 0 12px 40px rgba(0,0,0,0.5); overflow: hidden;
|
|
3756
|
+
}
|
|
3757
|
+
.export-hd {
|
|
3758
|
+
padding: 14px 18px; font-size: 13px; font-weight: 700;
|
|
3759
|
+
border-bottom: 1px solid #21262d; color: #e6edf3;
|
|
3760
|
+
}
|
|
3761
|
+
.export-modes { display: flex; gap: 6px; padding: 12px 18px 0; }
|
|
3762
|
+
.export-mode {
|
|
3763
|
+
flex: 1; padding: 7px 10px; font-size: 11px; border-radius: 6px;
|
|
3764
|
+
background: #0d1117; border: 1px solid #30363d; color: #8b949e;
|
|
3765
|
+
}
|
|
3766
|
+
.export-mode:hover { background: #21262d; color: #e6edf3; }
|
|
3767
|
+
.export-mode.active { background: rgba(47,129,247,0.15); border-color: #2f81f7; color: #2f81f7; }
|
|
3768
|
+
.export-body {
|
|
3769
|
+
padding: 14px 18px; overflow-y: auto; flex: 1; min-height: 160px;
|
|
3770
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
3771
|
+
}
|
|
3772
|
+
.export-tag-hint { font-size: 12px; color: #484f58; margin-top: 12px; }
|
|
3773
|
+
.export-result-label {
|
|
3774
|
+
font-size: 10px; color: #484f58; text-transform: uppercase; letter-spacing: 0.6px;
|
|
3775
|
+
font-weight: 600; margin: 14px 0 6px;
|
|
3776
|
+
}
|
|
3777
|
+
.export-row-static { cursor: default; }
|
|
3778
|
+
.export-row-static:hover { background: transparent; }
|
|
3779
|
+
.export-all-desc { font-size: 12px; color: #8b949e; }
|
|
3780
|
+
.export-empty { font-size: 12px; color: #484f58; text-align: center; padding: 20px; }
|
|
3781
|
+
.export-list { display: flex; flex-direction: column; gap: 4px; }
|
|
3782
|
+
.export-row {
|
|
3783
|
+
display: flex; align-items: center; gap: 8px;
|
|
3784
|
+
padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 12px; color: #c9d1d9;
|
|
3785
|
+
}
|
|
3786
|
+
.export-row:hover { background: #0d1117; }
|
|
3787
|
+
.export-row-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3788
|
+
.export-row-tag {
|
|
3789
|
+
font-size: 9px; color: #8b949e; background: #0d1117;
|
|
3790
|
+
border: 1px solid #30363d; border-radius: 10px; padding: 1px 7px; flex-shrink: 0;
|
|
3791
|
+
}
|
|
3792
|
+
.export-tags { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
3793
|
+
.export-tag {
|
|
3794
|
+
padding: 4px 10px; font-size: 11px; border-radius: 12px;
|
|
3795
|
+
background: #0d1117; border: 1px solid #30363d; color: #8b949e;
|
|
3796
|
+
}
|
|
3797
|
+
.export-tag:hover { background: #21262d; color: #e6edf3; }
|
|
3798
|
+
.export-tag.active { background: rgba(47,129,247,0.15); border-color: #2f81f7; color: #2f81f7; }
|
|
3799
|
+
.export-ft {
|
|
3800
|
+
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
|
3801
|
+
padding: 12px 18px; border-top: 1px solid #21262d;
|
|
3802
|
+
}
|
|
3803
|
+
.export-count { font-size: 11px; color: #8b949e; }
|
|
3804
|
+
.export-count b { color: #e6edf3; }
|
|
3805
|
+
.export-ft-actions { display: flex; gap: 8px; }
|
|
3806
|
+
.btn-export-confirm { background: #238636; border-color: #238636; color: #fff; }
|
|
3807
|
+
.btn-export-confirm:hover:not(:disabled) { background: #2ea043; border-color: #2ea043; }
|
|
3808
|
+
.btn-export-confirm:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
3391
3809
|
`;
|
|
3392
3810
|
|
|
3393
3811
|
// src/components/configuration/configuration.template.ts
|
|
3812
|
+
init_html_utils();
|
|
3394
3813
|
var LANGS = [
|
|
3395
3814
|
{ value: "es", label: "Espa\xF1ol" },
|
|
3396
3815
|
{ value: "en", label: "English" },
|
|
@@ -3398,8 +3817,64 @@ var LANGS = [
|
|
|
3398
3817
|
{ value: "it", label: "Italiano" },
|
|
3399
3818
|
{ value: "de", label: "Deutsch" }
|
|
3400
3819
|
];
|
|
3820
|
+
function renderExportOverlay(state, t) {
|
|
3821
|
+
const { isExporting, exportMode, exportTests, exportSelectedIds, exportSelectedTags } = state;
|
|
3822
|
+
if (!isExporting) return "";
|
|
3823
|
+
const count = selectTestsForExport(exportTests, exportMode, {
|
|
3824
|
+
ids: exportSelectedIds,
|
|
3825
|
+
tags: exportSelectedTags
|
|
3826
|
+
}).length;
|
|
3827
|
+
const modeBtn = (mode, key) => `<button class="export-mode ${exportMode === mode ? "active" : ""}" data-export-mode="${mode}">${t(key)}</button>`;
|
|
3828
|
+
let body;
|
|
3829
|
+
if (exportTests.length === 0) {
|
|
3830
|
+
body = `<div class="export-empty">${t("CONFIG.EXPORT_EMPTY")}</div>`;
|
|
3831
|
+
} else if (exportMode === "all") {
|
|
3832
|
+
body = `<div class="export-all-desc">${t("CONFIG.EXPORT_ALL_DESC")}</div>`;
|
|
3833
|
+
} else if (exportMode === "manual") {
|
|
3834
|
+
body = `<div class="export-list">${exportTests.map((test) => `
|
|
3835
|
+
<label class="export-row">
|
|
3836
|
+
<input type="checkbox" data-export-test="${test.id}" ${exportSelectedIds.has(test.id) ? "checked" : ""} />
|
|
3837
|
+
<span class="export-row-name">${escHtml(test.name)}</span>
|
|
3838
|
+
${(test.tags ?? []).map((tag) => `<span class="export-row-tag">${escHtml(tag)}</span>`).join("")}
|
|
3839
|
+
</label>`).join("")}</div>`;
|
|
3840
|
+
} else {
|
|
3841
|
+
const allTags = [...new Set(exportTests.flatMap((test) => test.tags ?? []))].sort();
|
|
3842
|
+
if (!allTags.length) {
|
|
3843
|
+
body = `<div class="export-empty">${t("CONFIG.EXPORT_NO_TAGS")}</div>`;
|
|
3844
|
+
} else {
|
|
3845
|
+
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>`;
|
|
3846
|
+
const matched = selectTestsForExport(exportTests, "tags", { tags: exportSelectedTags });
|
|
3847
|
+
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>
|
|
3848
|
+
<div class="export-list">${matched.map((test) => `
|
|
3849
|
+
<div class="export-row export-row-static">
|
|
3850
|
+
<span class="export-row-name">${escHtml(test.name)}</span>
|
|
3851
|
+
${(test.tags ?? []).map((tag) => `<span class="export-row-tag">${escHtml(tag)}</span>`).join("")}
|
|
3852
|
+
</div>`).join("")}</div>`;
|
|
3853
|
+
body = chips + preview;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
return `
|
|
3857
|
+
<div class="export-overlay" id="export-overlay">
|
|
3858
|
+
<div class="export-modal">
|
|
3859
|
+
<div class="export-hd">${t("CONFIG.EXPORT_DIALOG_TITLE")}</div>
|
|
3860
|
+
${exportTests.length ? `<div class="export-modes">
|
|
3861
|
+
${modeBtn("all", "CONFIG.EXPORT_MODE_ALL")}
|
|
3862
|
+
${modeBtn("manual", "CONFIG.EXPORT_MODE_MANUAL")}
|
|
3863
|
+
${modeBtn("tags", "CONFIG.EXPORT_MODE_TAGS")}
|
|
3864
|
+
</div>` : ""}
|
|
3865
|
+
<div class="export-body">${body}</div>
|
|
3866
|
+
<div class="export-ft">
|
|
3867
|
+
<span class="export-count">${t("CONFIG.EXPORT_COUNT")} <b>${count}</b></span>
|
|
3868
|
+
<span class="export-ft-actions">
|
|
3869
|
+
<button id="btn-export-confirm" class="btn-export-confirm" ${count === 0 ? "disabled" : ""}>${t("CONFIG.EXPORT_CONFIRM")}</button>
|
|
3870
|
+
<button id="btn-export-cancel">${t("CONFIG.EXPORT_CANCEL")}</button>
|
|
3871
|
+
</span>
|
|
3872
|
+
</div>
|
|
3873
|
+
</div>
|
|
3874
|
+
</div>`;
|
|
3875
|
+
}
|
|
3401
3876
|
function renderConfiguration(state, t) {
|
|
3402
|
-
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden } = state;
|
|
3877
|
+
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes } = state;
|
|
3403
3878
|
const langOptions = LANGS.map(
|
|
3404
3879
|
(l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
|
|
3405
3880
|
).join("");
|
|
@@ -3451,6 +3926,26 @@ function renderConfiguration(state, t) {
|
|
|
3451
3926
|
</label>
|
|
3452
3927
|
</div>
|
|
3453
3928
|
|
|
3929
|
+
<!-- Widget position (draggable widget) -->
|
|
3930
|
+
<div class="card">
|
|
3931
|
+
<div class="card-hd">${t("CONFIG.WIDGET_POSITION_SECTION")}</div>
|
|
3932
|
+
<div class="check-sub" style="margin-bottom:10px">${t("CONFIG.WIDGET_POSITION_HINT")}</div>
|
|
3933
|
+
<div class="btn-row">
|
|
3934
|
+
<button id="btn-reset-position">${t("CONFIG.WIDGET_POSITION_RESET_BTN")}</button>
|
|
3935
|
+
</div>
|
|
3936
|
+
</div>
|
|
3937
|
+
|
|
3938
|
+
<!-- Recording continuity (cross-app resume TTL) -->
|
|
3939
|
+
<div class="card">
|
|
3940
|
+
<div class="card-hd">${t("CONFIG.RESUME_TTL_SECTION")}</div>
|
|
3941
|
+
<div class="field-row">
|
|
3942
|
+
<span class="field-label">${t("CONFIG.RESUME_TTL_LABEL")}</span>
|
|
3943
|
+
<input type="number" id="resume-ttl-input" min="1" step="1" value="${resumeTtlMinutes}"
|
|
3944
|
+
style="width:80px;padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;color:#c9d1d9;font-size:12px;outline:none" />
|
|
3945
|
+
</div>
|
|
3946
|
+
<div class="check-sub" style="margin-top:8px">${t("CONFIG.RESUME_TTL_HINT")}</div>
|
|
3947
|
+
</div>
|
|
3948
|
+
|
|
3454
3949
|
<!-- Selector Strategy -->
|
|
3455
3950
|
<div class="card card-wide">
|
|
3456
3951
|
<div class="card-hd">${t("CONFIG.SELECTOR_SECTION")}</div>
|
|
@@ -3470,13 +3965,13 @@ function renderConfiguration(state, t) {
|
|
|
3470
3965
|
<div class="card card-wide">
|
|
3471
3966
|
<div class="card-hd">${t("CONFIG.FOLDER_SECTION")}</div>
|
|
3472
3967
|
<div class="fs-layout">
|
|
3473
|
-
<pre class="fs-tree">cypress/ <span style="color:#484f58"
|
|
3968
|
+
<pre class="fs-tree">cypress/ <span style="color:#484f58">${t("RECORDER.FS_TREE_PICK_HINT")}</span>
|
|
3474
3969
|
\u2514\u2500\u2500 e2e/
|
|
3475
3970
|
\u2514\u2500\u2500 *.cy.ts</pre>
|
|
3476
3971
|
<div class="fs-right">
|
|
3477
3972
|
<div class="fs-status">
|
|
3478
3973
|
<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>`}
|
|
3974
|
+
${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
3975
|
</div>
|
|
3481
3976
|
<div class="btn-row">
|
|
3482
3977
|
<button id="btn-change-folder">
|
|
@@ -3501,7 +3996,7 @@ function renderConfiguration(state, t) {
|
|
|
3501
3996
|
</div>
|
|
3502
3997
|
</div>
|
|
3503
3998
|
|
|
3504
|
-
</div
|
|
3999
|
+
</div>${renderExportOverlay(state, t)}`;
|
|
3505
4000
|
}
|
|
3506
4001
|
|
|
3507
4002
|
// src/components/configuration/configuration.ts
|
|
@@ -3514,6 +4009,12 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3514
4009
|
selectorStrategy = "data-cy";
|
|
3515
4010
|
smartSelectorEnabled = true;
|
|
3516
4011
|
startHidden = false;
|
|
4012
|
+
resumeTtlMinutes = DEFAULT_RESUME_TTL_MINUTES;
|
|
4013
|
+
isExporting = false;
|
|
4014
|
+
exportMode = "all";
|
|
4015
|
+
exportTests = [];
|
|
4016
|
+
exportSelectedIds = /* @__PURE__ */ new Set();
|
|
4017
|
+
exportSelectedTags = /* @__PURE__ */ new Set();
|
|
3517
4018
|
filesystemGranted = false;
|
|
3518
4019
|
cypressFolderName = null;
|
|
3519
4020
|
constructor() {
|
|
@@ -3540,6 +4041,11 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3540
4041
|
this.selectorStrategy = config?.["selectorStrategy"] ?? "data-cy";
|
|
3541
4042
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
3542
4043
|
this.startHidden = config?.["startHidden"] === "true";
|
|
4044
|
+
const ttlRaw = config?.[RESUME_TTL_CONFIG_KEY];
|
|
4045
|
+
if (ttlRaw !== void 0 && ttlRaw !== null) {
|
|
4046
|
+
const ttl = Number(ttlRaw);
|
|
4047
|
+
this.resumeTtlMinutes = Number.isFinite(ttl) && ttl > 0 ? ttl : DEFAULT_RESUME_TTL_MINUTES;
|
|
4048
|
+
}
|
|
3543
4049
|
this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
|
|
3544
4050
|
const handle = config?.["cypressDirectoryHandle"];
|
|
3545
4051
|
this.cypressFolderName = handle?.name ?? null;
|
|
@@ -3563,6 +4069,16 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3563
4069
|
this.dispatchEvent(new CustomEvent("starthiddenchange", { detail: checked, bubbles: true, composed: true }));
|
|
3564
4070
|
this.render();
|
|
3565
4071
|
}
|
|
4072
|
+
async onResumeTtlChange(minutes) {
|
|
4073
|
+
const safe = Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes) : DEFAULT_RESUME_TTL_MINUTES;
|
|
4074
|
+
this.resumeTtlMinutes = safe;
|
|
4075
|
+
await this.persistence.setConfig({ [RESUME_TTL_CONFIG_KEY]: safe });
|
|
4076
|
+
this.render();
|
|
4077
|
+
}
|
|
4078
|
+
onResetWidgetPosition() {
|
|
4079
|
+
this.dispatchEvent(new CustomEvent("resetwidgetposition", { bubbles: true, composed: true }));
|
|
4080
|
+
showToast(this.t("CONFIG.WIDGET_POSITION_RESET_DONE"));
|
|
4081
|
+
}
|
|
3566
4082
|
async onSmartSelectorChange(enabled) {
|
|
3567
4083
|
this.smartSelectorEnabled = enabled;
|
|
3568
4084
|
await this.persistence.setConfig({ smartSelectorEnabled: enabled ? "true" : "false" });
|
|
@@ -3592,8 +4108,7 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3592
4108
|
this.cypressFolderName = null;
|
|
3593
4109
|
this.render();
|
|
3594
4110
|
}
|
|
3595
|
-
|
|
3596
|
-
const tests = await this.persistence.getAllTests();
|
|
4111
|
+
downloadTests(tests) {
|
|
3597
4112
|
const blob = new Blob([JSON.stringify({ tests, interceptors: [] }, null, 2)], { type: "application/json" });
|
|
3598
4113
|
const url = URL.createObjectURL(blob);
|
|
3599
4114
|
const a = document.createElement("a");
|
|
@@ -3602,6 +4117,53 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3602
4117
|
a.click();
|
|
3603
4118
|
URL.revokeObjectURL(url);
|
|
3604
4119
|
}
|
|
4120
|
+
/** Downloads every saved test (used by the "Todo" mode and as a direct API). */
|
|
4121
|
+
async exportAllData() {
|
|
4122
|
+
const tests = await this.persistence.getAllTests();
|
|
4123
|
+
this.downloadTests(tests);
|
|
4124
|
+
}
|
|
4125
|
+
/** Opens the export selection dialog, loading the current tests. */
|
|
4126
|
+
async openExportDialog() {
|
|
4127
|
+
this.exportTests = await this.persistence.getAllTests();
|
|
4128
|
+
this.exportMode = "all";
|
|
4129
|
+
this.exportSelectedIds = /* @__PURE__ */ new Set();
|
|
4130
|
+
this.exportSelectedTags = /* @__PURE__ */ new Set();
|
|
4131
|
+
this.isExporting = true;
|
|
4132
|
+
this.render();
|
|
4133
|
+
}
|
|
4134
|
+
cancelExport() {
|
|
4135
|
+
this.isExporting = false;
|
|
4136
|
+
this.exportSelectedIds.clear();
|
|
4137
|
+
this.exportSelectedTags.clear();
|
|
4138
|
+
this.render();
|
|
4139
|
+
}
|
|
4140
|
+
setExportMode(mode) {
|
|
4141
|
+
this.exportMode = mode;
|
|
4142
|
+
this.render();
|
|
4143
|
+
}
|
|
4144
|
+
toggleExportTest(id) {
|
|
4145
|
+
if (this.exportSelectedIds.has(id)) this.exportSelectedIds.delete(id);
|
|
4146
|
+
else this.exportSelectedIds.add(id);
|
|
4147
|
+
this.render();
|
|
4148
|
+
}
|
|
4149
|
+
toggleExportTag(tag) {
|
|
4150
|
+
if (this.exportSelectedTags.has(tag)) this.exportSelectedTags.delete(tag);
|
|
4151
|
+
else this.exportSelectedTags.add(tag);
|
|
4152
|
+
this.render();
|
|
4153
|
+
}
|
|
4154
|
+
/** Downloads the tests resolved by the current mode + selection. No-op if empty. */
|
|
4155
|
+
confirmExport() {
|
|
4156
|
+
const subset = selectTestsForExport(this.exportTests, this.exportMode, {
|
|
4157
|
+
ids: this.exportSelectedIds,
|
|
4158
|
+
tags: this.exportSelectedTags
|
|
4159
|
+
});
|
|
4160
|
+
if (!subset.length) return;
|
|
4161
|
+
this.downloadTests(subset);
|
|
4162
|
+
this.isExporting = false;
|
|
4163
|
+
this.exportSelectedIds.clear();
|
|
4164
|
+
this.exportSelectedTags.clear();
|
|
4165
|
+
this.render();
|
|
4166
|
+
}
|
|
3605
4167
|
async importAllData(file) {
|
|
3606
4168
|
const text = await file.text();
|
|
3607
4169
|
let data;
|
|
@@ -3613,7 +4175,6 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3613
4175
|
if (!data || !Array.isArray(data.tests) || !Array.isArray(data.interceptors)) {
|
|
3614
4176
|
throw new Error(this.t("CONFIG.JSON_BAD_FORMAT"));
|
|
3615
4177
|
}
|
|
3616
|
-
await this.persistence.clearAllData();
|
|
3617
4178
|
await this.persistence.ingestFileData(data.tests, data.interceptors);
|
|
3618
4179
|
}
|
|
3619
4180
|
render() {
|
|
@@ -3624,7 +4185,13 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3624
4185
|
filesystemGranted: this.filesystemGranted,
|
|
3625
4186
|
cypressFolderName: this.cypressFolderName,
|
|
3626
4187
|
smartSelectorEnabled: this.smartSelectorEnabled,
|
|
3627
|
-
startHidden: this.startHidden
|
|
4188
|
+
startHidden: this.startHidden,
|
|
4189
|
+
resumeTtlMinutes: this.resumeTtlMinutes,
|
|
4190
|
+
isExporting: this.isExporting,
|
|
4191
|
+
exportMode: this.exportMode,
|
|
4192
|
+
exportTests: this.exportTests,
|
|
4193
|
+
exportSelectedIds: this.exportSelectedIds,
|
|
4194
|
+
exportSelectedTags: this.exportSelectedTags
|
|
3628
4195
|
}, this.t.bind(this))}`;
|
|
3629
4196
|
;
|
|
3630
4197
|
this.shadow.getElementById("lang-select").addEventListener(
|
|
@@ -3643,13 +4210,29 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3643
4210
|
"change",
|
|
3644
4211
|
(e) => this.onStartHiddenChange(e.target.checked)
|
|
3645
4212
|
);
|
|
4213
|
+
this.shadow.getElementById("resume-ttl-input").addEventListener(
|
|
4214
|
+
"change",
|
|
4215
|
+
(e) => this.onResumeTtlChange(Number(e.target.value))
|
|
4216
|
+
);
|
|
4217
|
+
this.shadow.getElementById("btn-reset-position")?.addEventListener("click", () => this.onResetWidgetPosition());
|
|
3646
4218
|
this.shadow.getElementById("selector-strategy").addEventListener(
|
|
3647
4219
|
"change",
|
|
3648
4220
|
(e) => this.onSelectorStrategyChange(e.target.value)
|
|
3649
4221
|
);
|
|
3650
4222
|
this.shadow.getElementById("btn-change-folder")?.addEventListener("click", () => this.changeFolder());
|
|
3651
4223
|
this.shadow.getElementById("btn-revoke")?.addEventListener("click", () => this.revokeAccess());
|
|
3652
|
-
this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.
|
|
4224
|
+
this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.openExportDialog());
|
|
4225
|
+
this.shadow.getElementById("btn-export-confirm")?.addEventListener("click", () => this.confirmExport());
|
|
4226
|
+
this.shadow.getElementById("btn-export-cancel")?.addEventListener("click", () => this.cancelExport());
|
|
4227
|
+
this.shadow.querySelectorAll("[data-export-mode]").forEach(
|
|
4228
|
+
(el) => el.addEventListener("click", () => this.setExportMode(el.dataset["exportMode"]))
|
|
4229
|
+
);
|
|
4230
|
+
this.shadow.querySelectorAll("[data-export-test]").forEach(
|
|
4231
|
+
(el) => el.addEventListener("change", () => this.toggleExportTest(Number(el.dataset["exportTest"])))
|
|
4232
|
+
);
|
|
4233
|
+
this.shadow.querySelectorAll("[data-export-tag]").forEach(
|
|
4234
|
+
(el) => el.addEventListener("click", () => this.toggleExportTag(el.dataset["exportTag"] ?? ""))
|
|
4235
|
+
);
|
|
3653
4236
|
this.shadow.getElementById("file-input").addEventListener("change", async (e) => {
|
|
3654
4237
|
const file = e.target.files?.[0];
|
|
3655
4238
|
if (!file) return;
|
|
@@ -3759,18 +4342,21 @@ var ADVANCED_TEST_EDITOR_STYLES = `
|
|
|
3759
4342
|
}
|
|
3760
4343
|
.btn-copy:hover { background: #30363d; color: #e6edf3; }
|
|
3761
4344
|
.sidebar-toolbar {
|
|
3762
|
-
display: flex; gap: 4px; padding: 6px 6px 4px;
|
|
4345
|
+
display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 6px 4px;
|
|
3763
4346
|
border-bottom: 1px solid #21262d; flex-shrink: 0; background: #0d1117;
|
|
3764
4347
|
}
|
|
3765
4348
|
.btn-toolbar {
|
|
3766
|
-
flex: 1; padding: 4px 6px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
|
|
4349
|
+
flex: 1 1 calc(50% - 2px); padding: 4px 6px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
|
|
3767
4350
|
font-size: 10px; font-weight: 500; background: #161b22; color: #8b949e;
|
|
3768
4351
|
transition: background 0.12s, color 0.12s; white-space: nowrap; overflow: hidden;
|
|
3769
4352
|
text-overflow: ellipsis;
|
|
3770
4353
|
}
|
|
4354
|
+
.btn-toolbar.btn-full { flex: 1 1 100%; }
|
|
3771
4355
|
.btn-toolbar:hover { background: #30363d; color: #e6edf3; }
|
|
3772
4356
|
.btn-toolbar.btn-new { color: #3fb950; border-color: #238636; }
|
|
3773
4357
|
.btn-toolbar.btn-new:hover { background: #238636; color: #fff; }
|
|
4358
|
+
.btn-toolbar.btn-new-folder { color: #d29922; border-color: #9e6a03; }
|
|
4359
|
+
.btn-toolbar.btn-new-folder:hover { background: #9e6a03; color: #fff; }
|
|
3774
4360
|
.new-file-form {
|
|
3775
4361
|
padding: 6px; border-bottom: 1px solid #21262d; display: flex; flex-direction: column; gap: 4px;
|
|
3776
4362
|
}
|
|
@@ -3804,7 +4390,7 @@ function renderNoPermission(needsReauth, t) {
|
|
|
3804
4390
|
</div>`;
|
|
3805
4391
|
}
|
|
3806
4392
|
function renderAdvancedEditor(state, t) {
|
|
3807
|
-
const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, testNotes, saveButtonEnabled, isCreatingFile, collapsedDirs, sidebarWidth } = state;
|
|
4393
|
+
const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, testNotes, saveButtonEnabled, isCreatingFile, isCreatingFolder, collapsedDirs, sidebarWidth } = state;
|
|
3808
4394
|
const treeHtml = e2eTree.length ? renderTree(e2eTree, selectedFile, collapsedDirs) : `<div class="tree-item" style="color:#6c7a99">${t("ADVANCED_EDITOR.NO_FILES")}</div>`;
|
|
3809
4395
|
const contentHtml = selectedFileContent ? `<div class="file-name">\u{1F4C4} ${escHtml(selectedFile?.name ?? "")}</div>
|
|
3810
4396
|
<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 +4417,25 @@ function renderAdvancedEditor(state, t) {
|
|
|
3831
4417
|
<button id="btn-new-file-cancel" class="btn-cancel-form">${t("ADVANCED_EDITOR.NEW_FILE_CANCEL")}</button>
|
|
3832
4418
|
</div>
|
|
3833
4419
|
</div>` : "";
|
|
4420
|
+
const newFolderForm = isCreatingFolder ? `<div class="new-file-form">
|
|
4421
|
+
<input id="input-new-folder" type="text" placeholder="${escHtml(t("ADVANCED_EDITOR.NEW_FOLDER_PLACEHOLDER"))}" autocomplete="off" />
|
|
4422
|
+
<div class="new-file-actions">
|
|
4423
|
+
<button id="btn-new-folder-confirm" class="btn-confirm">${t("ADVANCED_EDITOR.NEW_FILE_CONFIRM")}</button>
|
|
4424
|
+
<button id="btn-new-folder-cancel" class="btn-cancel-form">${t("ADVANCED_EDITOR.NEW_FILE_CANCEL")}</button>
|
|
4425
|
+
</div>
|
|
4426
|
+
</div>` : "";
|
|
3834
4427
|
const toolbar = `
|
|
3835
4428
|
<div class="sidebar-toolbar">
|
|
3836
4429
|
<button id="btn-new-file" class="btn-toolbar btn-new">${t("ADVANCED_EDITOR.NEW_FILE_BTN")}</button>
|
|
3837
|
-
<button id="btn-
|
|
4430
|
+
<button id="btn-new-folder" class="btn-toolbar btn-new-folder">${t("ADVANCED_EDITOR.NEW_FOLDER_BTN")}</button>
|
|
4431
|
+
<button id="btn-refresh" class="btn-toolbar btn-full">${t("ADVANCED_EDITOR.REFRESH_BTN")}</button>
|
|
3838
4432
|
</div>`;
|
|
3839
4433
|
return `
|
|
3840
4434
|
<div class="layout">
|
|
3841
4435
|
<div class="sidebar" style="width:${sidebarWidth}px">
|
|
3842
4436
|
${toolbar}
|
|
3843
4437
|
${newFileForm}
|
|
4438
|
+
${newFolderForm}
|
|
3844
4439
|
<div class="tree-scroll">${treeHtml}</div>
|
|
3845
4440
|
</div>
|
|
3846
4441
|
<div id="resize-handle" class="resize-handle"></div>
|
|
@@ -3911,6 +4506,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3911
4506
|
previewFileName = null;
|
|
3912
4507
|
previewFileContent = null;
|
|
3913
4508
|
isCreatingFile = false;
|
|
4509
|
+
isCreatingFolder = false;
|
|
3914
4510
|
collapsedDirs = /* @__PURE__ */ new Set();
|
|
3915
4511
|
sidebarWidth = 220;
|
|
3916
4512
|
hasPermission = false;
|
|
@@ -3973,7 +4569,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3973
4569
|
const name = rawName.trim().replace(/\.cy\.ts$/, "");
|
|
3974
4570
|
if (!name) return;
|
|
3975
4571
|
const fileName = `${name}.cy.ts`;
|
|
3976
|
-
const template = `describe('${name}', () => {
|
|
4572
|
+
const template = `describe('${escapeSingleQuotes(name)}', () => {
|
|
3977
4573
|
|
|
3978
4574
|
it('should ', () => {
|
|
3979
4575
|
|
|
@@ -3991,6 +4587,17 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3991
4587
|
this.isCreatingFile = false;
|
|
3992
4588
|
await this.getFoldersData();
|
|
3993
4589
|
}
|
|
4590
|
+
async createNewFolder(rawName) {
|
|
4591
|
+
if (!this._e2eHandle) return;
|
|
4592
|
+
const name = rawName.trim().replace(/[/\\]/g, "");
|
|
4593
|
+
if (!name) return;
|
|
4594
|
+
try {
|
|
4595
|
+
await this._e2eHandle.getDirectoryHandle(name, { create: true });
|
|
4596
|
+
} catch {
|
|
4597
|
+
}
|
|
4598
|
+
this.isCreatingFolder = false;
|
|
4599
|
+
await this.getFoldersData();
|
|
4600
|
+
}
|
|
3994
4601
|
async refreshTree() {
|
|
3995
4602
|
await this.getFoldersData();
|
|
3996
4603
|
}
|
|
@@ -4048,7 +4655,8 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
4048
4655
|
fileName: this.selectedFile?.name ?? "",
|
|
4049
4656
|
testId: this.testId,
|
|
4050
4657
|
itBlock: this.testItBlock,
|
|
4051
|
-
interceptorsBlock: this.interceptorsBlock
|
|
4658
|
+
interceptorsBlock: this.interceptorsBlock,
|
|
4659
|
+
notes: this.testNotes
|
|
4052
4660
|
},
|
|
4053
4661
|
bubbles: true,
|
|
4054
4662
|
composed: true
|
|
@@ -4125,6 +4733,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
4125
4733
|
testNotes: this.testNotes,
|
|
4126
4734
|
saveButtonEnabled: this.saveButtonEnabled,
|
|
4127
4735
|
isCreatingFile: this.isCreatingFile,
|
|
4736
|
+
isCreatingFolder: this.isCreatingFolder,
|
|
4128
4737
|
collapsedDirs: this.collapsedDirs,
|
|
4129
4738
|
sidebarWidth: this.sidebarWidth
|
|
4130
4739
|
}, this.t.bind(this))}`;
|
|
@@ -4148,9 +4757,16 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
4148
4757
|
this.shadow.getElementById("btn-close")?.addEventListener("click", () => this.closePreview());
|
|
4149
4758
|
this.shadow.getElementById("btn-new-file")?.addEventListener("click", () => {
|
|
4150
4759
|
this.isCreatingFile = !this.isCreatingFile;
|
|
4760
|
+
this.isCreatingFolder = false;
|
|
4151
4761
|
this.render();
|
|
4152
4762
|
if (this.isCreatingFile) this.shadow.getElementById("input-new-file")?.focus();
|
|
4153
4763
|
});
|
|
4764
|
+
this.shadow.getElementById("btn-new-folder")?.addEventListener("click", () => {
|
|
4765
|
+
this.isCreatingFolder = !this.isCreatingFolder;
|
|
4766
|
+
this.isCreatingFile = false;
|
|
4767
|
+
this.render();
|
|
4768
|
+
if (this.isCreatingFolder) this.shadow.getElementById("input-new-folder")?.focus();
|
|
4769
|
+
});
|
|
4154
4770
|
this.shadow.getElementById("btn-refresh")?.addEventListener("click", () => this.refreshTree());
|
|
4155
4771
|
this.shadow.getElementById("btn-new-file-confirm")?.addEventListener("click", () => {
|
|
4156
4772
|
const input2 = this.shadow.getElementById("input-new-file");
|
|
@@ -4169,6 +4785,23 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
4169
4785
|
this.render();
|
|
4170
4786
|
}
|
|
4171
4787
|
});
|
|
4788
|
+
this.shadow.getElementById("btn-new-folder-confirm")?.addEventListener("click", () => {
|
|
4789
|
+
const folderInput2 = this.shadow.getElementById("input-new-folder");
|
|
4790
|
+
this.createNewFolder(folderInput2?.value ?? "");
|
|
4791
|
+
});
|
|
4792
|
+
this.shadow.getElementById("btn-new-folder-cancel")?.addEventListener("click", () => {
|
|
4793
|
+
this.isCreatingFolder = false;
|
|
4794
|
+
this.render();
|
|
4795
|
+
});
|
|
4796
|
+
const folderInput = this.shadow.getElementById("input-new-folder");
|
|
4797
|
+
folderInput?.addEventListener("keydown", (e) => {
|
|
4798
|
+
if (e.key === "Enter") {
|
|
4799
|
+
this.createNewFolder(folderInput.value);
|
|
4800
|
+
} else if (e.key === "Escape") {
|
|
4801
|
+
this.isCreatingFolder = false;
|
|
4802
|
+
this.render();
|
|
4803
|
+
}
|
|
4804
|
+
});
|
|
4172
4805
|
}
|
|
4173
4806
|
};
|
|
4174
4807
|
if (!customElements.get("advanced-test-editor")) {
|
|
@@ -4254,7 +4887,30 @@ var FILE_PREVIEW_STYLES = `
|
|
|
4254
4887
|
.btn-save:hover { background: #2ea043; border-color: #2ea043; }
|
|
4255
4888
|
.btn-launch { background: transparent; border-color: #e3b341; color: #e3b341; }
|
|
4256
4889
|
.btn-launch:hover { background: rgba(227,179,65,0.1); }
|
|
4890
|
+
.btn-launch:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
4891
|
+
.btn-launch:disabled:hover { background: transparent; }
|
|
4892
|
+
.launch-hint { font-size: 10px; color: #8b6d3b; align-self: center; }
|
|
4893
|
+
.btn-insert { background: transparent; border-color: #a371f7; color: #a371f7; }
|
|
4894
|
+
.btn-insert:hover { background: rgba(163,113,247,0.12); color: #c8a8ff; }
|
|
4257
4895
|
.btn-diff-active { background: rgba(47,129,247,0.15); border-color: rgba(47,129,247,0.4); color: #2f81f7; }
|
|
4896
|
+
|
|
4897
|
+
/* \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 */
|
|
4898
|
+
.run-result {
|
|
4899
|
+
border-top: 1px solid #21262d; padding: 8px 12px; background: #0d1117;
|
|
4900
|
+
max-height: 220px; overflow-y: auto; flex-shrink: 0;
|
|
4901
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
4902
|
+
}
|
|
4903
|
+
.run-status { font-size: 12px; font-weight: 600; }
|
|
4904
|
+
.run-passed .run-status { color: #3fb950; }
|
|
4905
|
+
.run-failed .run-status { color: #f85149; }
|
|
4906
|
+
.run-running .run-status { color: #e3b341; }
|
|
4907
|
+
.run-error .run-status { color: #f85149; }
|
|
4908
|
+
.run-output {
|
|
4909
|
+
margin: 6px 0 0; padding: 8px 10px; background: #161b22; border: 1px solid #21262d;
|
|
4910
|
+
border-radius: 5px; font-size: 10px; color: #c9d1d9; line-height: 1.5;
|
|
4911
|
+
font-family: 'Cascadia Code','Fira Code','Consolas',monospace;
|
|
4912
|
+
white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow-y: auto;
|
|
4913
|
+
}
|
|
4258
4914
|
`;
|
|
4259
4915
|
|
|
4260
4916
|
// src/components/file-preview/file-preview.template.ts
|
|
@@ -4312,8 +4968,14 @@ function buildDiffHtml(original, current, t) {
|
|
|
4312
4968
|
return `<div class="diff-line ${type}"><span class="diff-sign">${sign}</span><span>${escHtml(line)}</span></div>`;
|
|
4313
4969
|
}).join("");
|
|
4314
4970
|
}
|
|
4971
|
+
var RUN_STATUS_KEY = {
|
|
4972
|
+
running: "FILE_PREVIEW.LAUNCH_RUNNING",
|
|
4973
|
+
passed: "FILE_PREVIEW.LAUNCH_PASSED",
|
|
4974
|
+
failed: "FILE_PREVIEW.LAUNCH_FAILED",
|
|
4975
|
+
error: "FILE_PREVIEW.LAUNCH_NO_RUNNER"
|
|
4976
|
+
};
|
|
4315
4977
|
function renderFilePreview(state, t) {
|
|
4316
|
-
const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel } = state;
|
|
4978
|
+
const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel, isLocal, runState, runOutput } = state;
|
|
4317
4979
|
const blocksPanelHtml = itBlock || interceptorsBlock ? `
|
|
4318
4980
|
<div class="blocks-panel">
|
|
4319
4981
|
${itBlock ? `
|
|
@@ -4334,6 +4996,13 @@ function renderFilePreview(state, t) {
|
|
|
4334
4996
|
</div>` : ""}
|
|
4335
4997
|
</div>` : "";
|
|
4336
4998
|
const hasChanges = originalContent !== null && originalContent !== (fileContent ?? "");
|
|
4999
|
+
const hasBlocks = !!(itBlock || interceptorsBlock);
|
|
5000
|
+
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>
|
|
5001
|
+
<span class="launch-hint">${t("FILE_PREVIEW.LAUNCH_LOCAL_ONLY")}</span>`;
|
|
5002
|
+
const runResultHtml = runState !== "idle" ? `<div class="run-result run-${runState}">
|
|
5003
|
+
<span class="run-status">${t(RUN_STATUS_KEY[runState])}</span>
|
|
5004
|
+
${runOutput ? `<pre class="run-output">${escHtml(runOutput.slice(0, 8e3))}</pre>` : ""}
|
|
5005
|
+
</div>` : "";
|
|
4337
5006
|
return `
|
|
4338
5007
|
<div class="container">
|
|
4339
5008
|
<div class="header">
|
|
@@ -4344,9 +5013,11 @@ function renderFilePreview(state, t) {
|
|
|
4344
5013
|
${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea>`}
|
|
4345
5014
|
${blocksPanelHtml}
|
|
4346
5015
|
</div>
|
|
5016
|
+
${runResultHtml}
|
|
4347
5017
|
<div class="footer">
|
|
4348
|
-
|
|
5018
|
+
${launchBtnHtml}
|
|
4349
5019
|
${hasChanges ? `<button id="btn-diff" class="${showDiff ? "btn-diff-active" : ""}">${t("FILE_PREVIEW.DIFF_BTN")}</button>` : ""}
|
|
5020
|
+
${hasBlocks && !showDiff ? `<button id="btn-insert" class="btn-insert" title="${t("FILE_PREVIEW.INSERT_TITLE")}">${t("FILE_PREVIEW.INSERT_BTN")}</button>` : ""}
|
|
4350
5021
|
<button id="btn-save" class="btn-save">${t("FILE_PREVIEW.SAVE_BTN")}</button>
|
|
4351
5022
|
<button id="btn-close">${escHtml(closeLabel || t("FILE_PREVIEW.CLOSE_BTN"))}</button>
|
|
4352
5023
|
</div>
|
|
@@ -4362,11 +5033,18 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4362
5033
|
translation = translationService;
|
|
4363
5034
|
itBlock = "";
|
|
4364
5035
|
interceptorsBlock = "";
|
|
5036
|
+
notes = "";
|
|
4365
5037
|
commands = [];
|
|
4366
5038
|
interceptors = [];
|
|
5039
|
+
/** Endpoint of the local Cypress runner. Configurable; defaults to the bundled runner's port. */
|
|
5040
|
+
runnerUrl = "http://localhost:8123/run-test";
|
|
5041
|
+
/** Whether the app is served locally — gates the launch button. Overridable for tests. */
|
|
5042
|
+
isLocal = isLocalHost(window.location.hostname);
|
|
4367
5043
|
_fileContent = null;
|
|
4368
5044
|
_originalContent = null;
|
|
4369
5045
|
_showDiff = false;
|
|
5046
|
+
_runState = "idle";
|
|
5047
|
+
_runOutput = "";
|
|
4370
5048
|
constructor() {
|
|
4371
5049
|
super();
|
|
4372
5050
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -4394,15 +5072,34 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4394
5072
|
onClose() {
|
|
4395
5073
|
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
4396
5074
|
}
|
|
5075
|
+
/**
|
|
5076
|
+
* Runs the current spec headless via the local runner and shows the result.
|
|
5077
|
+
* No-op off localhost. Never throws — connection failures surface as an
|
|
5078
|
+
* "error" state + toast instead of an unhandled rejection.
|
|
5079
|
+
*/
|
|
4397
5080
|
async launchTest(specPath) {
|
|
4398
|
-
|
|
5081
|
+
if (!this.isLocal) return;
|
|
5082
|
+
const path = specPath ?? this.fileName ?? "";
|
|
4399
5083
|
if (!path) return;
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
5084
|
+
this._runState = "running";
|
|
5085
|
+
this._runOutput = "";
|
|
5086
|
+
this.render();
|
|
5087
|
+
try {
|
|
5088
|
+
const response = await fetch(this.runnerUrl, {
|
|
5089
|
+
method: "POST",
|
|
5090
|
+
headers: { "Content-Type": "application/json" },
|
|
5091
|
+
body: JSON.stringify({ specPath: path })
|
|
5092
|
+
});
|
|
5093
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
5094
|
+
const result = await response.json();
|
|
5095
|
+
this._runOutput = result.output ?? "";
|
|
5096
|
+
this._runState = result.success ? "passed" : "failed";
|
|
5097
|
+
} catch {
|
|
5098
|
+
this._runState = "error";
|
|
5099
|
+
this._runOutput = "";
|
|
5100
|
+
showToast(this.t("FILE_PREVIEW.LAUNCH_NO_RUNNER"), false);
|
|
5101
|
+
}
|
|
5102
|
+
this.render();
|
|
4406
5103
|
}
|
|
4407
5104
|
copyToClipboard(text) {
|
|
4408
5105
|
navigator.clipboard?.writeText(text);
|
|
@@ -4411,6 +5108,32 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4411
5108
|
this._showDiff = !this._showDiff;
|
|
4412
5109
|
this.render();
|
|
4413
5110
|
}
|
|
5111
|
+
/**
|
|
5112
|
+
* Merges the it() and beforeEach() blocks into the current editor content
|
|
5113
|
+
* using the same logic as the automatic "Insert into file" action, so the
|
|
5114
|
+
* user does not have to copy/paste manually. Leaves the content untouched
|
|
5115
|
+
* (and warns) when the file has no valid describe() block to insert into.
|
|
5116
|
+
*/
|
|
5117
|
+
insertBlocks() {
|
|
5118
|
+
const base = this.textarea?.value ?? this._fileContent ?? "";
|
|
5119
|
+
let content = base;
|
|
5120
|
+
if (this.interceptorsBlock) {
|
|
5121
|
+
const merged = advancedTestTransformationService.insertBeforeEach(content, this.interceptorsBlock);
|
|
5122
|
+
if (merged) content = merged;
|
|
5123
|
+
}
|
|
5124
|
+
if (this.itBlock) {
|
|
5125
|
+
const comment = this.notes ? advancedTestTransformationService.buildBlockComment(this.notes) + "\n" : "";
|
|
5126
|
+
const merged = advancedTestTransformationService.insertItBlock(content, comment + this.itBlock);
|
|
5127
|
+
if (merged) content = merged;
|
|
5128
|
+
}
|
|
5129
|
+
if (content === base) {
|
|
5130
|
+
showToast(this.t("FILE_PREVIEW.INSERT_ERROR"), false);
|
|
5131
|
+
return;
|
|
5132
|
+
}
|
|
5133
|
+
this._fileContent = content;
|
|
5134
|
+
this.render();
|
|
5135
|
+
showToast(this.t("FILE_PREVIEW.INSERT_DONE"));
|
|
5136
|
+
}
|
|
4414
5137
|
render() {
|
|
4415
5138
|
this.shadow.innerHTML = `<style>${FILE_PREVIEW_STYLES}</style>${renderFilePreview({
|
|
4416
5139
|
fileName: this.fileName,
|
|
@@ -4420,7 +5143,10 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4420
5143
|
currentContent: this.textarea?.value ?? this._fileContent ?? "",
|
|
4421
5144
|
itBlock: this.itBlock,
|
|
4422
5145
|
interceptorsBlock: this.interceptorsBlock,
|
|
4423
|
-
closeLabel: this.closeLabel
|
|
5146
|
+
closeLabel: this.closeLabel,
|
|
5147
|
+
isLocal: this.isLocal,
|
|
5148
|
+
runState: this._runState,
|
|
5149
|
+
runOutput: this._runOutput
|
|
4424
5150
|
}, this.t.bind(this))}`;
|
|
4425
5151
|
this.textarea = this.shadow.getElementById("editor");
|
|
4426
5152
|
if (this.textarea) {
|
|
@@ -4436,6 +5162,7 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
4436
5162
|
this.copyToClipboard(this.textarea?.value ?? this._fileContent ?? "");
|
|
4437
5163
|
});
|
|
4438
5164
|
this.shadow.getElementById("btn-diff")?.addEventListener("click", () => this.toggleDiff());
|
|
5165
|
+
this.shadow.getElementById("btn-insert")?.addEventListener("click", () => this.insertBlocks());
|
|
4439
5166
|
this.shadow.getElementById("btn-copy-it")?.addEventListener("click", () => {
|
|
4440
5167
|
this.copyToClipboard(this.itBlock);
|
|
4441
5168
|
});
|
|
@@ -4452,15 +5179,37 @@ if (!customElements.get("file-preview")) {
|
|
|
4452
5179
|
var import_sweetalert2 = __toESM(require("sweetalert2"), 1);
|
|
4453
5180
|
|
|
4454
5181
|
// src/components/lib-e2e-recorder/lib-e2e-recorder.styles.ts
|
|
5182
|
+
var DIRECTIONS = ["up-left", "up-right", "down-left", "down-right"];
|
|
5183
|
+
function directionBlocks() {
|
|
5184
|
+
return DIRECTIONS.map((dir) => {
|
|
5185
|
+
const sx = dir.endsWith("left") ? -1 : 1;
|
|
5186
|
+
const sy = dir.startsWith("up") ? -1 : 1;
|
|
5187
|
+
const tv = dir.startsWith("up") ? "bottom" : "top";
|
|
5188
|
+
const th = dir.endsWith("left") ? "right" : "left";
|
|
5189
|
+
const ov = tv === "bottom" ? "top" : "bottom";
|
|
5190
|
+
const oh = th === "right" ? "left" : "right";
|
|
5191
|
+
const sel = `.widget[data-expand="${dir}"]`;
|
|
5192
|
+
const labelGeneral = `${sel} .btn-action::after { ${oh}: calc(100% + 9px); ${th}: auto; top: 50%; bottom: auto; transform: translateY(-50%); }`;
|
|
5193
|
+
const labelEnds = `${sel} .btn-action[data-n="1"]::after, ${sel} .btn-action[data-n="4"]::after { ${ov}: calc(100% + 9px); ${tv}: auto; left: 50%; right: auto; transform: translateX(-50%); }`;
|
|
5194
|
+
return `
|
|
5195
|
+
${sel} { --sx: ${sx}; --sy: ${sy}; }
|
|
5196
|
+
${sel} .btn-toggle { ${tv}: 24px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
|
|
5197
|
+
${sel} .btn-pause { ${tv}: 78px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
|
|
5198
|
+
${sel} .btn-action { ${tv}: 28px; ${th}: 28px; ${ov}: auto; ${oh}: auto; }
|
|
5199
|
+
${labelGeneral}
|
|
5200
|
+
${labelEnds}
|
|
5201
|
+
`;
|
|
5202
|
+
}).join("\n");
|
|
5203
|
+
}
|
|
4455
5204
|
function getRecorderStyles(rec, paused) {
|
|
4456
5205
|
return `
|
|
4457
5206
|
:host { all: initial; }
|
|
4458
5207
|
*, *::before, *::after { box-sizing: border-box; }
|
|
4459
5208
|
|
|
4460
5209
|
/*
|
|
4461
|
-
* Invisible 190\xD7190 hit area
|
|
4462
|
-
*
|
|
4463
|
-
*
|
|
5210
|
+
* Invisible 190\xD7190 hit area. Position (left/top) is set from JS so the
|
|
5211
|
+
* widget can be dragged; --sx/--sy + data-expand orient the radial menu so
|
|
5212
|
+
* it always expands toward the viewport interior. Defaults to bottom-right.
|
|
4464
5213
|
*/
|
|
4465
5214
|
.widget {
|
|
4466
5215
|
position: fixed;
|
|
@@ -4469,6 +5218,8 @@ function getRecorderStyles(rec, paused) {
|
|
|
4469
5218
|
width: 190px;
|
|
4470
5219
|
height: 190px;
|
|
4471
5220
|
z-index: 2147483647;
|
|
5221
|
+
--sx: -1;
|
|
5222
|
+
--sy: -1;
|
|
4472
5223
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
4473
5224
|
}
|
|
4474
5225
|
|
|
@@ -4481,7 +5232,8 @@ function getRecorderStyles(rec, paused) {
|
|
|
4481
5232
|
height: 44px;
|
|
4482
5233
|
border-radius: 50%;
|
|
4483
5234
|
border: none;
|
|
4484
|
-
cursor:
|
|
5235
|
+
cursor: grab;
|
|
5236
|
+
touch-action: none;
|
|
4485
5237
|
font-size: 19px;
|
|
4486
5238
|
background: ${rec ? "linear-gradient(135deg,#f85149 0%,#da3633 100%)" : "linear-gradient(135deg,#2f81f7 0%,#1f6feb 100%)"};
|
|
4487
5239
|
color: #fff;
|
|
@@ -4494,7 +5246,7 @@ function getRecorderStyles(rec, paused) {
|
|
|
4494
5246
|
${rec ? "animation: toggle-pulse 2s ease-in-out infinite;" : ""}
|
|
4495
5247
|
}
|
|
4496
5248
|
.btn-toggle:hover { transform: scale(1.1); }
|
|
4497
|
-
.btn-toggle:active { transform: scale(0.93); }
|
|
5249
|
+
.btn-toggle:active { transform: scale(0.93); cursor: grabbing; }
|
|
4498
5250
|
|
|
4499
5251
|
@keyframes toggle-pulse {
|
|
4500
5252
|
0%,100% { box-shadow: 0 4px 20px rgba(248,81,73,.55),0 0 0 4px rgba(248,81,73,.13); }
|
|
@@ -4525,11 +5277,6 @@ function getRecorderStyles(rec, paused) {
|
|
|
4525
5277
|
.btn-pause:active { transform: scale(0.93); }
|
|
4526
5278
|
|
|
4527
5279
|
/* \u2500\u2500 Action buttons \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 */
|
|
4528
|
-
/*
|
|
4529
|
-
* All three start centered on the toggle button
|
|
4530
|
-
* (toggle center = bottom:46px right:46px from widget edge;
|
|
4531
|
-
* button half = 18px \u2192 bottom:28px right:28px).
|
|
4532
|
-
*/
|
|
4533
5280
|
.btn-action {
|
|
4534
5281
|
position: absolute;
|
|
4535
5282
|
bottom: 28px;
|
|
@@ -4551,12 +5298,11 @@ function getRecorderStyles(rec, paused) {
|
|
|
4551
5298
|
opacity: 0;
|
|
4552
5299
|
transform: scale(0.35);
|
|
4553
5300
|
pointer-events: none;
|
|
4554
|
-
/* Collapse: fast, no spring */
|
|
4555
5301
|
transition: opacity .15s, transform .18s ease-in,
|
|
4556
5302
|
background .15s, color .12s, box-shadow .15s;
|
|
4557
5303
|
}
|
|
4558
5304
|
|
|
4559
|
-
/* Label
|
|
5305
|
+
/* Label chip */
|
|
4560
5306
|
.btn-action::after {
|
|
4561
5307
|
content: attr(data-label);
|
|
4562
5308
|
position: absolute;
|
|
@@ -4576,14 +5322,6 @@ function getRecorderStyles(rec, paused) {
|
|
|
4576
5322
|
border: 1px solid rgba(48,54,61,.8);
|
|
4577
5323
|
box-shadow: 0 2px 8px rgba(0,0,0,.35);
|
|
4578
5324
|
}
|
|
4579
|
-
/* Button 1 (top) \u2014 label above instead of left to avoid overlap with btn 2 */
|
|
4580
|
-
.btn-action[data-n="1"]::after {
|
|
4581
|
-
right: auto;
|
|
4582
|
-
left: 50%;
|
|
4583
|
-
top: auto;
|
|
4584
|
-
bottom: calc(100% + 9px);
|
|
4585
|
-
transform: translateX(-50%);
|
|
4586
|
-
}
|
|
4587
5325
|
.btn-action:hover::after { opacity: 1; }
|
|
4588
5326
|
.btn-action:hover { background: #21262d; color: #e6edf3; }
|
|
4589
5327
|
.btn-action:active { background: #30363d !important; }
|
|
@@ -4596,31 +5334,26 @@ function getRecorderStyles(rec, paused) {
|
|
|
4596
5334
|
background .15s, color .12s, box-shadow .15s;
|
|
4597
5335
|
}
|
|
4598
5336
|
|
|
4599
|
-
/* Arc positions \u2014
|
|
4600
|
-
.widget:hover .btn-action[data-n="1"] { /*
|
|
4601
|
-
transform: translateY(
|
|
5337
|
+
/* Arc positions \u2014 signs come from --sx/--sy (data-expand) */
|
|
5338
|
+
.widget:hover .btn-action[data-n="1"] { /* vertical extreme */
|
|
5339
|
+
transform: translateY(calc(var(--sy) * 90px)) scale(1);
|
|
4602
5340
|
transition-delay: .03s;
|
|
4603
5341
|
}
|
|
4604
|
-
.widget:hover .btn-action[data-n="2"] { /*
|
|
4605
|
-
transform: translate(
|
|
5342
|
+
.widget:hover .btn-action[data-n="2"] { /* diagonal near-vertical */
|
|
5343
|
+
transform: translate(calc(var(--sx) * 45px), calc(var(--sy) * 78px)) scale(1);
|
|
4606
5344
|
transition-delay: .07s;
|
|
4607
5345
|
}
|
|
4608
|
-
.widget:hover .btn-action[data-n="3"] { /*
|
|
4609
|
-
transform: translate(
|
|
5346
|
+
.widget:hover .btn-action[data-n="3"] { /* diagonal near-horizontal */
|
|
5347
|
+
transform: translate(calc(var(--sx) * 78px), calc(var(--sy) * 45px)) scale(1);
|
|
4610
5348
|
transition-delay: .11s;
|
|
4611
5349
|
}
|
|
4612
|
-
.widget:hover .btn-action[data-n="4"] { /*
|
|
4613
|
-
transform: translateX(
|
|
5350
|
+
.widget:hover .btn-action[data-n="4"] { /* horizontal extreme */
|
|
5351
|
+
transform: translateX(calc(var(--sx) * 90px)) scale(1);
|
|
4614
5352
|
transition-delay: .15s;
|
|
4615
5353
|
}
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
left: 50%;
|
|
4620
|
-
top: auto;
|
|
4621
|
-
bottom: calc(100% + 9px);
|
|
4622
|
-
transform: translateX(-50%);
|
|
4623
|
-
}
|
|
5354
|
+
|
|
5355
|
+
/* Per-direction anchors + label sides */
|
|
5356
|
+
${directionBlocks()}
|
|
4624
5357
|
|
|
4625
5358
|
/* \u2500\u2500 REC / PAUSED badge \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 */
|
|
4626
5359
|
.rec-badge {
|
|
@@ -4683,7 +5416,18 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4683
5416
|
interceptorsUnsub;
|
|
4684
5417
|
pauseUnsub;
|
|
4685
5418
|
selectorNotFoundUnsub;
|
|
5419
|
+
langUnsub;
|
|
5420
|
+
sessionUnsub;
|
|
5421
|
+
sessionSaveTimer;
|
|
4686
5422
|
controlFirstTimeData = true;
|
|
5423
|
+
// ── draggable widget (spec 007) ──
|
|
5424
|
+
togglePos = null;
|
|
5425
|
+
expandDir = "up-left";
|
|
5426
|
+
dragState;
|
|
5427
|
+
suppressNextToggleClick = false;
|
|
5428
|
+
widgetPointerMove;
|
|
5429
|
+
widgetPointerUp;
|
|
5430
|
+
widgetResize;
|
|
4687
5431
|
_previsualizerRef = null;
|
|
4688
5432
|
httpMonitor;
|
|
4689
5433
|
smartSelectorEnabled = true;
|
|
@@ -4726,17 +5470,31 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4726
5470
|
this.initSubscriptions();
|
|
4727
5471
|
this.render();
|
|
4728
5472
|
this.initVisibility();
|
|
5473
|
+
this.initSessionContinuity();
|
|
5474
|
+
this.initWidgetPosition();
|
|
4729
5475
|
this.keydownHandler = (e) => this.handleKeyboardEvent(e);
|
|
4730
5476
|
window.addEventListener("keydown", this.keydownHandler);
|
|
5477
|
+
this.widgetPointerMove = (e) => this.onWidgetPointerMove(e);
|
|
5478
|
+
this.widgetPointerUp = () => this.onWidgetPointerUp();
|
|
5479
|
+
this.widgetResize = () => this.applyWidgetPosition();
|
|
5480
|
+
window.addEventListener("pointermove", this.widgetPointerMove);
|
|
5481
|
+
window.addEventListener("pointerup", this.widgetPointerUp);
|
|
5482
|
+
window.addEventListener("resize", this.widgetResize);
|
|
4731
5483
|
}
|
|
4732
5484
|
disconnectedCallback() {
|
|
4733
5485
|
if (this._isDisabled) return;
|
|
4734
5486
|
window.removeEventListener("keydown", this.keydownHandler);
|
|
5487
|
+
this.flushActiveSessionOnDisconnect();
|
|
4735
5488
|
this.recordingUnsub?.();
|
|
4736
5489
|
this.commandsUnsub?.();
|
|
4737
5490
|
this.interceptorsUnsub?.();
|
|
4738
5491
|
this.pauseUnsub?.();
|
|
4739
5492
|
this.selectorNotFoundUnsub?.();
|
|
5493
|
+
this.langUnsub?.();
|
|
5494
|
+
this.sessionUnsub?.();
|
|
5495
|
+
if (this.widgetPointerMove) window.removeEventListener("pointermove", this.widgetPointerMove);
|
|
5496
|
+
if (this.widgetPointerUp) window.removeEventListener("pointerup", this.widgetPointerUp);
|
|
5497
|
+
if (this.widgetResize) window.removeEventListener("resize", this.widgetResize);
|
|
4740
5498
|
this.httpMonitor?.uninstall();
|
|
4741
5499
|
this.recording.destroy();
|
|
4742
5500
|
}
|
|
@@ -4759,11 +5517,13 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4759
5517
|
this.isRecording = val;
|
|
4760
5518
|
if (!val && !this.controlFirstTimeData) {
|
|
4761
5519
|
this.saveRecordingHistory();
|
|
5520
|
+
this.clearSessionPersistence();
|
|
4762
5521
|
this.showSaveTestDialog();
|
|
4763
5522
|
}
|
|
4764
5523
|
this.controlFirstTimeData = false;
|
|
4765
5524
|
this.render();
|
|
4766
5525
|
});
|
|
5526
|
+
this.sessionUnsub = this.recording.onSessionChange((state) => this.persistActiveSession(state));
|
|
4767
5527
|
this.commandsUnsub = this.recording.onCommandsChange((cmds) => {
|
|
4768
5528
|
this.cypressCommands = cmds;
|
|
4769
5529
|
if (this._previsualizerRef) this._previsualizerRef.commands = cmds;
|
|
@@ -4779,6 +5539,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4779
5539
|
this.selectorNotFoundUnsub = this.recording.onSelectorNotFound((target) => {
|
|
4780
5540
|
if (this.smartSelectorEnabled) this.showSelectorPicker(target);
|
|
4781
5541
|
});
|
|
5542
|
+
this.langUnsub = this.translation.onLangChange(() => this.render());
|
|
4782
5543
|
}
|
|
4783
5544
|
showSelectorPicker(target) {
|
|
4784
5545
|
Promise.resolve().then(() => (init_selector_picker(), selector_picker_exports)).then(() => {
|
|
@@ -4807,6 +5568,176 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4807
5568
|
if (strategy) this.recording.selectorStrategy = strategy;
|
|
4808
5569
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
4809
5570
|
}
|
|
5571
|
+
// ── cross-app session continuity (spec 006) ───────────────────────────────
|
|
5572
|
+
/**
|
|
5573
|
+
* On mount, detect a persisted live recording session and either resume it
|
|
5574
|
+
* silently (recent) or prompt continue/discard (stale). No-op when there is
|
|
5575
|
+
* no actively-recording session.
|
|
5576
|
+
*/
|
|
5577
|
+
async initSessionContinuity() {
|
|
5578
|
+
if (this._isDisabled) return;
|
|
5579
|
+
const session = await this.persistence.getActiveSession();
|
|
5580
|
+
if (!session || !session.isRecording) return;
|
|
5581
|
+
const ttlMin = await this.getResumeTtlMinutes();
|
|
5582
|
+
const ageMs = Date.now() - session.updatedAt;
|
|
5583
|
+
if (ageMs <= ttlMin * 6e4) {
|
|
5584
|
+
this.resumeSessionState(session);
|
|
5585
|
+
} else {
|
|
5586
|
+
this.promptResumeOrDiscard(session);
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
async getResumeTtlMinutes() {
|
|
5590
|
+
const config = await this.persistence.getConfig(RESUME_TTL_CONFIG_KEY);
|
|
5591
|
+
const raw = config?.[RESUME_TTL_CONFIG_KEY];
|
|
5592
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
5593
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_RESUME_TTL_MINUTES;
|
|
5594
|
+
}
|
|
5595
|
+
/** Rehydrates the recorder from a persisted session (no bootstrap re-emitted). */
|
|
5596
|
+
resumeSessionState(session) {
|
|
5597
|
+
this.recording.restoreSession(session);
|
|
5598
|
+
this.cypressCommands = session.commands;
|
|
5599
|
+
this.interceptors = session.interceptors;
|
|
5600
|
+
this.controlFirstTimeData = false;
|
|
5601
|
+
this.render();
|
|
5602
|
+
}
|
|
5603
|
+
async promptResumeOrDiscard(session) {
|
|
5604
|
+
const t = this.translation.translate.bind(this.translation);
|
|
5605
|
+
const result = await import_sweetalert2.default.fire({
|
|
5606
|
+
title: t("RECORDER.SESSION_RESUME_TITLE"),
|
|
5607
|
+
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>`,
|
|
5608
|
+
showCancelButton: true,
|
|
5609
|
+
confirmButtonText: t("RECORDER.SESSION_CONTINUE_BTN"),
|
|
5610
|
+
cancelButtonText: t("RECORDER.SESSION_DISCARD_BTN"),
|
|
5611
|
+
color: "#e6edf3"
|
|
5612
|
+
});
|
|
5613
|
+
if (result && result.isConfirmed) {
|
|
5614
|
+
this.resumeSessionState(session);
|
|
5615
|
+
} else {
|
|
5616
|
+
this.discardSession();
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
/** Debounced persistence of the live session; clears it the moment recording stops. */
|
|
5620
|
+
persistActiveSession(state) {
|
|
5621
|
+
if (this._isDisabled) return;
|
|
5622
|
+
if (!state.isRecording) {
|
|
5623
|
+
this.clearSessionPersistence();
|
|
5624
|
+
return;
|
|
5625
|
+
}
|
|
5626
|
+
this.writeSessionBreadcrumb(state);
|
|
5627
|
+
if (this.sessionSaveTimer) clearTimeout(this.sessionSaveTimer);
|
|
5628
|
+
this.sessionSaveTimer = setTimeout(() => {
|
|
5629
|
+
const snap = this.recording.getSessionSnapshot();
|
|
5630
|
+
if (snap.isRecording) this.persistence.saveActiveSession(snap).catch(() => {
|
|
5631
|
+
});
|
|
5632
|
+
}, 300);
|
|
5633
|
+
}
|
|
5634
|
+
writeSessionBreadcrumb(state) {
|
|
5635
|
+
try {
|
|
5636
|
+
localStorage.setItem(ACTIVE_SESSION_BREADCRUMB_KEY, JSON.stringify({
|
|
5637
|
+
sessionId: state.sessionId,
|
|
5638
|
+
isRecording: state.isRecording,
|
|
5639
|
+
updatedAt: state.updatedAt
|
|
5640
|
+
}));
|
|
5641
|
+
} catch {
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
flushActiveSessionOnDisconnect() {
|
|
5645
|
+
const snap = this.recording.getSessionSnapshot();
|
|
5646
|
+
if (!snap.isRecording) return;
|
|
5647
|
+
if (this.sessionSaveTimer) clearTimeout(this.sessionSaveTimer);
|
|
5648
|
+
this.writeSessionBreadcrumb(snap);
|
|
5649
|
+
this.persistence.saveActiveSession(snap).catch(() => {
|
|
5650
|
+
});
|
|
5651
|
+
}
|
|
5652
|
+
clearSessionPersistence() {
|
|
5653
|
+
if (this.sessionSaveTimer) {
|
|
5654
|
+
clearTimeout(this.sessionSaveTimer);
|
|
5655
|
+
this.sessionSaveTimer = void 0;
|
|
5656
|
+
}
|
|
5657
|
+
try {
|
|
5658
|
+
localStorage.removeItem(ACTIVE_SESSION_BREADCRUMB_KEY);
|
|
5659
|
+
} catch {
|
|
5660
|
+
}
|
|
5661
|
+
this.persistence.clearActiveSession().catch(() => {
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
/** True when a synchronous breadcrumb marks an active recording session. */
|
|
5665
|
+
hasActiveSession() {
|
|
5666
|
+
try {
|
|
5667
|
+
const raw = localStorage.getItem(ACTIVE_SESSION_BREADCRUMB_KEY);
|
|
5668
|
+
if (!raw) return false;
|
|
5669
|
+
return !!JSON.parse(raw).isRecording;
|
|
5670
|
+
} catch {
|
|
5671
|
+
return false;
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
/** Programmatically resume the persisted session, if any. */
|
|
5675
|
+
resumeSession() {
|
|
5676
|
+
this.persistence.getActiveSession().then((s) => {
|
|
5677
|
+
if (s) this.resumeSessionState(s);
|
|
5678
|
+
});
|
|
5679
|
+
}
|
|
5680
|
+
/** Discard the persisted live session (breadcrumb + DB record). */
|
|
5681
|
+
discardSession() {
|
|
5682
|
+
this.clearSessionPersistence();
|
|
5683
|
+
}
|
|
5684
|
+
// ── draggable widget (spec 007) ────────────────────────────────────────────
|
|
5685
|
+
/** Loads a previously saved widget position and applies it. */
|
|
5686
|
+
async initWidgetPosition() {
|
|
5687
|
+
const config = await this.persistence.getGeneralConfig();
|
|
5688
|
+
const pos = config?.["widgetPosition"];
|
|
5689
|
+
if (pos && typeof pos.x === "number" && typeof pos.y === "number") {
|
|
5690
|
+
this.togglePos = { x: pos.x, y: pos.y };
|
|
5691
|
+
}
|
|
5692
|
+
this.applyWidgetPosition();
|
|
5693
|
+
}
|
|
5694
|
+
/** Positions the `.widget` box from the (clamped) toggle centre and orients the arc. */
|
|
5695
|
+
applyWidgetPosition() {
|
|
5696
|
+
const widget = this.shadow.querySelector(".widget");
|
|
5697
|
+
if (!widget) return;
|
|
5698
|
+
const vw = window.innerWidth;
|
|
5699
|
+
const vh = window.innerHeight;
|
|
5700
|
+
const centre = this.togglePos ?? defaultTogglePosition(vw, vh);
|
|
5701
|
+
const clamped = clampTogglePosition(centre.x, centre.y, vw, vh);
|
|
5702
|
+
this.expandDir = resolveExpandDirection(clamped.x, clamped.y, vw, vh);
|
|
5703
|
+
const topLeft = boxTopLeftFor(clamped, this.expandDir);
|
|
5704
|
+
widget.style.left = `${topLeft.x}px`;
|
|
5705
|
+
widget.style.top = `${topLeft.y}px`;
|
|
5706
|
+
widget.style.right = "auto";
|
|
5707
|
+
widget.style.bottom = "auto";
|
|
5708
|
+
widget.setAttribute("data-expand", this.expandDir);
|
|
5709
|
+
}
|
|
5710
|
+
beginWidgetDrag(e) {
|
|
5711
|
+
const origin = this.togglePos ?? defaultTogglePosition(window.innerWidth, window.innerHeight);
|
|
5712
|
+
this.dragState = { startX: e.clientX, startY: e.clientY, origX: origin.x, origY: origin.y, moved: false };
|
|
5713
|
+
}
|
|
5714
|
+
onWidgetPointerMove(e) {
|
|
5715
|
+
if (!this.dragState) return;
|
|
5716
|
+
const dx = e.clientX - this.dragState.startX;
|
|
5717
|
+
const dy = e.clientY - this.dragState.startY;
|
|
5718
|
+
if (!this.dragState.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
5719
|
+
this.dragState.moved = true;
|
|
5720
|
+
this.togglePos = { x: this.dragState.origX + dx, y: this.dragState.origY + dy };
|
|
5721
|
+
this.applyWidgetPosition();
|
|
5722
|
+
}
|
|
5723
|
+
onWidgetPointerUp() {
|
|
5724
|
+
if (!this.dragState) return;
|
|
5725
|
+
const moved = this.dragState.moved;
|
|
5726
|
+
this.dragState = void 0;
|
|
5727
|
+
if (!moved || !this.togglePos) return;
|
|
5728
|
+
this.suppressNextToggleClick = true;
|
|
5729
|
+
const clamped = clampTogglePosition(this.togglePos.x, this.togglePos.y, window.innerWidth, window.innerHeight);
|
|
5730
|
+
this.togglePos = clamped;
|
|
5731
|
+
this.persistence.setConfig({ widgetPosition: clamped }).catch(() => {
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
/** Resets the widget to its default corner and clears the saved position. */
|
|
5735
|
+
resetWidgetPosition() {
|
|
5736
|
+
this.togglePos = null;
|
|
5737
|
+
this.persistence.setConfig({ widgetPosition: null }).catch(() => {
|
|
5738
|
+
});
|
|
5739
|
+
this.applyWidgetPosition();
|
|
5740
|
+
}
|
|
4810
5741
|
toggle() {
|
|
4811
5742
|
this.recording.toggleRecording();
|
|
4812
5743
|
}
|
|
@@ -5123,6 +6054,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5123
6054
|
this.isVisible = !e.detail;
|
|
5124
6055
|
this.style.display = this.isVisible ? "" : "none";
|
|
5125
6056
|
});
|
|
6057
|
+
child.addEventListener("resetwidgetposition", () => this.resetWidgetPosition());
|
|
5126
6058
|
},
|
|
5127
6059
|
willClose: () => {
|
|
5128
6060
|
this.isSettingsDialogOpen = false;
|
|
@@ -5177,7 +6109,8 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5177
6109
|
e.detail.fileName,
|
|
5178
6110
|
e.detail.testId,
|
|
5179
6111
|
e.detail.itBlock,
|
|
5180
|
-
e.detail.interceptorsBlock
|
|
6112
|
+
e.detail.interceptorsBlock,
|
|
6113
|
+
e.detail.notes
|
|
5181
6114
|
), 150);
|
|
5182
6115
|
});
|
|
5183
6116
|
},
|
|
@@ -5191,7 +6124,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5191
6124
|
}, 0);
|
|
5192
6125
|
});
|
|
5193
6126
|
}
|
|
5194
|
-
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "") {
|
|
6127
|
+
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "", notes = "") {
|
|
5195
6128
|
import_sweetalert2.default.fire({
|
|
5196
6129
|
title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
|
|
5197
6130
|
html: '<div id="file-editor-modal-content" style="padding:0;height:540px"></div>',
|
|
@@ -5212,6 +6145,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5212
6145
|
child.closeLabel = this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR");
|
|
5213
6146
|
child.itBlock = itBlock;
|
|
5214
6147
|
child.interceptorsBlock = interceptorsBlock;
|
|
6148
|
+
child.notes = notes;
|
|
5215
6149
|
container.appendChild(child);
|
|
5216
6150
|
child.addEventListener("close", () => {
|
|
5217
6151
|
import_sweetalert2.default.close();
|
|
@@ -5274,12 +6208,21 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
5274
6208
|
const paused = this.isPaused;
|
|
5275
6209
|
this.style.display = this.isVisible ? "" : "none";
|
|
5276
6210
|
this.shadow.innerHTML = `<style>${getRecorderStyles(rec, paused)}</style>${renderRecorderWidget(rec, paused, this.translation.translate.bind(this.translation))}`;
|
|
5277
|
-
this.shadow.querySelector('[data-action="toggle"]')
|
|
6211
|
+
const toggleBtn = this.shadow.querySelector('[data-action="toggle"]');
|
|
6212
|
+
toggleBtn?.addEventListener("click", () => {
|
|
6213
|
+
if (this.suppressNextToggleClick) {
|
|
6214
|
+
this.suppressNextToggleClick = false;
|
|
6215
|
+
return;
|
|
6216
|
+
}
|
|
6217
|
+
this.toggle();
|
|
6218
|
+
});
|
|
6219
|
+
toggleBtn?.addEventListener("pointerdown", (e) => this.beginWidgetDrag(e));
|
|
5278
6220
|
this.shadow.querySelector('[data-action="pause"]')?.addEventListener("click", () => this.togglePause());
|
|
5279
6221
|
this.shadow.querySelector('[data-action="tests"]')?.addEventListener("click", () => this.showSavedTestsDialog());
|
|
5280
6222
|
this.shadow.querySelector('[data-action="commands"]')?.addEventListener("click", () => this.showCommandsDialog());
|
|
5281
6223
|
this.shadow.querySelector('[data-action="config"]')?.addEventListener("click", () => this.showSettingsDialog());
|
|
5282
6224
|
this.shadow.querySelector('[data-action="browse"]')?.addEventListener("click", () => this.showAdvancedEditorDialog());
|
|
6225
|
+
this.applyWidgetPosition();
|
|
5283
6226
|
}
|
|
5284
6227
|
};
|
|
5285
6228
|
if (!customElements.get("lib-e2e-recorder")) {
|
|
@@ -5287,38 +6230,53 @@ if (!customElements.get("lib-e2e-recorder")) {
|
|
|
5287
6230
|
}
|
|
5288
6231
|
|
|
5289
6232
|
// src/index.ts
|
|
5290
|
-
var VERSION = "0.
|
|
6233
|
+
var VERSION = "0.6.0";
|
|
5291
6234
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5292
6235
|
0 && (module.exports = {
|
|
6236
|
+
ACTIVE_SESSION_BREADCRUMB_KEY,
|
|
5293
6237
|
AdvancedTestEditorElement,
|
|
5294
6238
|
AdvancedTestTransformationService,
|
|
5295
6239
|
ConfigurationElement,
|
|
5296
6240
|
DB_SCHEMA,
|
|
5297
6241
|
DB_STORE_NAMES,
|
|
6242
|
+
DEFAULT_RESUME_TTL_MINUTES,
|
|
6243
|
+
DRAG_THRESHOLD,
|
|
5298
6244
|
FilePreviewElement,
|
|
5299
6245
|
HttpMonitor,
|
|
5300
6246
|
INPUT_TYPES,
|
|
5301
6247
|
LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
|
|
6248
|
+
LOCALE_BY_LANG,
|
|
5302
6249
|
LibE2eRecorderElement,
|
|
5303
6250
|
PersistenceService,
|
|
6251
|
+
RESUME_TTL_CONFIG_KEY,
|
|
5304
6252
|
RecordingService,
|
|
5305
6253
|
SCROLLBAR_STYLES,
|
|
5306
6254
|
SUPPORTED_LANGS,
|
|
5307
6255
|
SaveTestElement,
|
|
6256
|
+
SelectorPickerElement,
|
|
5308
6257
|
Subject,
|
|
6258
|
+
TOGGLE_MARGIN,
|
|
5309
6259
|
TestEditorElement,
|
|
5310
6260
|
TestPrevisualizerElement,
|
|
5311
6261
|
TransformationService,
|
|
5312
6262
|
TranslationService,
|
|
5313
6263
|
VERSION,
|
|
5314
6264
|
advancedTestTransformationService,
|
|
6265
|
+
boxOffsetForDirection,
|
|
6266
|
+
boxTopLeftFor,
|
|
6267
|
+
clampTogglePosition,
|
|
6268
|
+
defaultTogglePosition,
|
|
5315
6269
|
generateAlias,
|
|
5316
6270
|
injectStyles,
|
|
5317
6271
|
isLang,
|
|
6272
|
+
isLocalHost,
|
|
6273
|
+
localeForLang,
|
|
5318
6274
|
makeModalResizable,
|
|
5319
6275
|
makeSwalDraggable,
|
|
5320
6276
|
makeSwalDraggableByContentId,
|
|
5321
6277
|
persistenceService,
|
|
6278
|
+
resolveExpandDirection,
|
|
6279
|
+
selectTestsForExport,
|
|
5322
6280
|
setSwal2DataCyAttribute,
|
|
5323
6281
|
showToast,
|
|
5324
6282
|
transformationService,
|