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.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FORBIDDEN_ID_PREFIXES,
|
|
3
|
+
SelectorPickerElement,
|
|
3
4
|
escAttr,
|
|
4
5
|
escHtml
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FDMUIHDZ.js";
|
|
6
7
|
|
|
7
8
|
// src/models/lang.model.ts
|
|
8
9
|
var SUPPORTED_LANGS = ["es", "en", "fr", "it", "de"];
|
|
9
10
|
function isLang(value) {
|
|
10
11
|
return SUPPORTED_LANGS.includes(value);
|
|
11
12
|
}
|
|
13
|
+
var LOCALE_BY_LANG = {
|
|
14
|
+
es: "es-ES",
|
|
15
|
+
en: "en-GB",
|
|
16
|
+
fr: "fr-FR",
|
|
17
|
+
it: "it-IT",
|
|
18
|
+
de: "de-DE"
|
|
19
|
+
};
|
|
20
|
+
function localeForLang(lang) {
|
|
21
|
+
return isLang(lang) ? LOCALE_BY_LANG[lang] : "es-ES";
|
|
22
|
+
}
|
|
12
23
|
|
|
13
24
|
// src/models/input-types.model.ts
|
|
14
25
|
var INPUT_TYPES = [
|
|
@@ -25,7 +36,7 @@ var INPUT_TYPES = [
|
|
|
25
36
|
// src/models/db-schema.model.ts
|
|
26
37
|
var DB_SCHEMA = {
|
|
27
38
|
name: "E2ECypressDB",
|
|
28
|
-
version:
|
|
39
|
+
version: 11,
|
|
29
40
|
stores: [
|
|
30
41
|
{
|
|
31
42
|
name: "tests",
|
|
@@ -65,11 +76,45 @@ var DB_SCHEMA = {
|
|
|
65
76
|
{ name: "extendedHttpCommands", keyPath: "extendedHttpCommands", unique: false },
|
|
66
77
|
{ name: "allowReadWriteFiles", keyPath: "allowReadWriteFiles", unique: false }
|
|
67
78
|
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
// Single-record store (fixed key id=1) holding the live, in-progress
|
|
82
|
+
// recording session so it survives a micro-frontend crossing or reload.
|
|
83
|
+
// See docs/specs/006-cross-app-recording-continuity.md.
|
|
84
|
+
name: "activeSession",
|
|
85
|
+
keyPath: "id",
|
|
86
|
+
autoIncrement: false,
|
|
87
|
+
indexes: []
|
|
68
88
|
}
|
|
69
89
|
]
|
|
70
90
|
};
|
|
71
91
|
var DB_STORE_NAMES = DB_SCHEMA.stores.map((s) => s.name);
|
|
72
92
|
|
|
93
|
+
// src/models/active-session.model.ts
|
|
94
|
+
var ACTIVE_SESSION_BREADCRUMB_KEY = "e2e-active-session";
|
|
95
|
+
var RESUME_TTL_CONFIG_KEY = "resumeRecencyTtlMinutes";
|
|
96
|
+
var DEFAULT_RESUME_TTL_MINUTES = 30;
|
|
97
|
+
|
|
98
|
+
// src/utils/subject.ts
|
|
99
|
+
var Subject = class {
|
|
100
|
+
_value;
|
|
101
|
+
listeners = /* @__PURE__ */ new Set();
|
|
102
|
+
constructor(initialValue) {
|
|
103
|
+
this._value = initialValue;
|
|
104
|
+
}
|
|
105
|
+
next(value) {
|
|
106
|
+
this._value = value;
|
|
107
|
+
this.listeners.forEach((l) => l(value));
|
|
108
|
+
}
|
|
109
|
+
getValue() {
|
|
110
|
+
return this._value;
|
|
111
|
+
}
|
|
112
|
+
subscribe(fn) {
|
|
113
|
+
this.listeners.add(fn);
|
|
114
|
+
return () => this.listeners.delete(fn);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
73
118
|
// src/i18n/es.ts
|
|
74
119
|
var I18N_ES = {
|
|
75
120
|
MAIN_FRAME: {
|
|
@@ -104,8 +149,6 @@ var I18N_ES = {
|
|
|
104
149
|
COPY_DESCRIBE: "\u{1F5C2} Copiar describe()",
|
|
105
150
|
MULTI_SELECT: "\u{1F5C2} Multi-select",
|
|
106
151
|
CANCEL_SELECT: "\u2715 Cancelar",
|
|
107
|
-
SECTION_COMMANDS: "Comandos",
|
|
108
|
-
SECTION_INTERCEPTORS: "Interceptores",
|
|
109
152
|
COPY_CMDS_BTN: "\u{1F4CB} Copiar comandos",
|
|
110
153
|
COPY_ICPS_BTN: "\u{1F4CB} Copiar interceptores",
|
|
111
154
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
@@ -134,6 +177,18 @@ var I18N_ES = {
|
|
|
134
177
|
DATA_SECTION: "\u{1F4BE} Datos",
|
|
135
178
|
DATA_DESC: "Exporta todos tus tests a JSON o importa una copia de seguridad.",
|
|
136
179
|
EXPORT_BTN: "\u2B06\uFE0F Exportar tests",
|
|
180
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Exportar tests",
|
|
181
|
+
EXPORT_MODE_ALL: "Todo",
|
|
182
|
+
EXPORT_MODE_MANUAL: "Selecci\xF3n manual",
|
|
183
|
+
EXPORT_MODE_TAGS: "Por tags",
|
|
184
|
+
EXPORT_ALL_DESC: "Se exportar\xE1n todos los tests guardados.",
|
|
185
|
+
EXPORT_COUNT: "A exportar:",
|
|
186
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exportar",
|
|
187
|
+
EXPORT_CANCEL: "Cancelar",
|
|
188
|
+
EXPORT_EMPTY: "No hay tests para exportar.",
|
|
189
|
+
EXPORT_NO_TAGS: "No hay tags disponibles.",
|
|
190
|
+
EXPORT_TAGS_HINT: "Selecciona uno o m\xE1s tags para ver qu\xE9 pruebas se exportar\xE1n.",
|
|
191
|
+
EXPORT_RESULT_LABEL: "Pruebas que se exportar\xE1n:",
|
|
137
192
|
IMPORT_BTN: "\u2B07\uFE0F Importar tests",
|
|
138
193
|
FOLDER_UPDATED_TOAST: "\u2713 Carpeta de Cypress actualizada",
|
|
139
194
|
FOLDER_ERROR_TOAST: "Error al acceder a la carpeta",
|
|
@@ -146,7 +201,14 @@ var I18N_ES = {
|
|
|
146
201
|
SMART_SELECTOR_SUB: "Muestra un picker al hacer click en elementos sin selector v\xE1lido.",
|
|
147
202
|
START_HIDDEN_SECTION: "\u{1F441} Visibilidad del widget",
|
|
148
203
|
START_HIDDEN_TITLE: "Iniciar oculto",
|
|
149
|
-
START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo."
|
|
204
|
+
START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo.",
|
|
205
|
+
RESUME_TTL_SECTION: "\u23F1 Continuidad de grabaci\xF3n",
|
|
206
|
+
RESUME_TTL_LABEL: "Reanudar sin preguntar (minutos)",
|
|
207
|
+
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.",
|
|
208
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Posici\xF3n del widget",
|
|
209
|
+
WIDGET_POSITION_HINT: "Arrastra el bot\xF3n de grabar para moverlo donde no estorbe. Se recuerda su posici\xF3n.",
|
|
210
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA Restablecer posici\xF3n",
|
|
211
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida"
|
|
150
212
|
},
|
|
151
213
|
SELECTOR_PICKER: {
|
|
152
214
|
TITLE: "Selecciona un elemento del DOM",
|
|
@@ -187,8 +249,10 @@ var I18N_ES = {
|
|
|
187
249
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Editar manualmente",
|
|
188
250
|
CLOSE_BTN: "\u2715 Cerrar",
|
|
189
251
|
NEW_FILE_BTN: "+ Nuevo archivo",
|
|
252
|
+
NEW_FOLDER_BTN: "+ Nueva carpeta",
|
|
190
253
|
REFRESH_BTN: "\u21BB Actualizar",
|
|
191
254
|
NEW_FILE_PLACEHOLDER: "nombre-del-test",
|
|
255
|
+
NEW_FOLDER_PLACEHOLDER: "nombre-de-carpeta",
|
|
192
256
|
NEW_FILE_CONFIRM: "Crear",
|
|
193
257
|
NEW_FILE_CANCEL: "Cancelar"
|
|
194
258
|
},
|
|
@@ -201,10 +265,19 @@ var I18N_ES = {
|
|
|
201
265
|
NO_CHANGES: "Sin cambios respecto al original",
|
|
202
266
|
NO_CHANGES_SHORT: "Sin cambios",
|
|
203
267
|
LAUNCH_BTN: "\u25B6 Lanzar test",
|
|
268
|
+
LAUNCH_LOCAL_ONLY: "Mu\xE9velo a local para poder probar",
|
|
269
|
+
LAUNCH_RUNNING: "\u23F3 Ejecutando\u2026",
|
|
270
|
+
LAUNCH_PASSED: "\u2713 Prueba superada",
|
|
271
|
+
LAUNCH_FAILED: "\u2717 Prueba fallida",
|
|
272
|
+
LAUNCH_NO_RUNNER: "No se detect\xF3 el runner local (\xBFlo has arrancado?)",
|
|
204
273
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
205
274
|
SAVE_BTN: "\u{1F4BE} Guardar",
|
|
206
275
|
CLOSE_BTN: "\u2715 Cerrar",
|
|
207
|
-
BACK_TO_EDITOR: "\u2190 Volver al editor"
|
|
276
|
+
BACK_TO_EDITOR: "\u2190 Volver al editor",
|
|
277
|
+
INSERT_BTN: "\u{1FA84} Insertar bloques",
|
|
278
|
+
INSERT_TITLE: "Insertar it() y beforeEach() en el contenido del editor",
|
|
279
|
+
INSERT_DONE: "Bloques insertados en el editor",
|
|
280
|
+
INSERT_ERROR: "No se encontr\xF3 un bloque describe() v\xE1lido en el archivo"
|
|
208
281
|
},
|
|
209
282
|
RECORDER: {
|
|
210
283
|
FS_TITLE: "\u{1F4C1} Acceso a ficheros",
|
|
@@ -233,7 +306,12 @@ var I18N_ES = {
|
|
|
233
306
|
BADGE_PAUSED: "\u23F8 PAUSA",
|
|
234
307
|
BADGE_REC: "\u25CF REC",
|
|
235
308
|
STOP_TITLE: "Detener (Ctrl+R)",
|
|
236
|
-
START_TITLE: "Grabar (Ctrl+R)"
|
|
309
|
+
START_TITLE: "Grabar (Ctrl+R)",
|
|
310
|
+
SESSION_RESUME_TITLE: "\u23FA Grabaci\xF3n en curso",
|
|
311
|
+
SESSION_RESUME_TEXT: "Hay una grabaci\xF3n activa sin terminar.",
|
|
312
|
+
SESSION_RESUME_COUNT: "comandos capturados",
|
|
313
|
+
SESSION_CONTINUE_BTN: "Continuar grabando",
|
|
314
|
+
SESSION_DISCARD_BTN: "Descartar"
|
|
237
315
|
}
|
|
238
316
|
};
|
|
239
317
|
|
|
@@ -271,8 +349,6 @@ var I18N_EN = {
|
|
|
271
349
|
COPY_DESCRIBE: "\u{1F5C2} Copy describe()",
|
|
272
350
|
MULTI_SELECT: "\u{1F5C2} Multi-select",
|
|
273
351
|
CANCEL_SELECT: "\u2715 Cancel",
|
|
274
|
-
SECTION_COMMANDS: "Commands",
|
|
275
|
-
SECTION_INTERCEPTORS: "Interceptors",
|
|
276
352
|
COPY_CMDS_BTN: "\u{1F4CB} Copy commands",
|
|
277
353
|
COPY_ICPS_BTN: "\u{1F4CB} Copy interceptors",
|
|
278
354
|
DEFAULT_DESCRIBE: "Test suite",
|
|
@@ -301,6 +377,18 @@ var I18N_EN = {
|
|
|
301
377
|
DATA_SECTION: "\u{1F4BE} Data",
|
|
302
378
|
DATA_DESC: "Export all your tests to JSON or import a backup.",
|
|
303
379
|
EXPORT_BTN: "\u2B06\uFE0F Export tests",
|
|
380
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Export tests",
|
|
381
|
+
EXPORT_MODE_ALL: "All",
|
|
382
|
+
EXPORT_MODE_MANUAL: "Manual selection",
|
|
383
|
+
EXPORT_MODE_TAGS: "By tags",
|
|
384
|
+
EXPORT_ALL_DESC: "All saved tests will be exported.",
|
|
385
|
+
EXPORT_COUNT: "To export:",
|
|
386
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Export",
|
|
387
|
+
EXPORT_CANCEL: "Cancel",
|
|
388
|
+
EXPORT_EMPTY: "No tests to export.",
|
|
389
|
+
EXPORT_NO_TAGS: "No tags available.",
|
|
390
|
+
EXPORT_TAGS_HINT: "Select one or more tags to preview which tests will be exported.",
|
|
391
|
+
EXPORT_RESULT_LABEL: "Tests that will be exported:",
|
|
304
392
|
IMPORT_BTN: "\u2B07\uFE0F Import tests",
|
|
305
393
|
FOLDER_UPDATED_TOAST: "\u2713 Cypress folder updated",
|
|
306
394
|
FOLDER_ERROR_TOAST: "Error accessing the folder",
|
|
@@ -313,7 +401,14 @@ var I18N_EN = {
|
|
|
313
401
|
SMART_SELECTOR_SUB: "Shows a picker when clicking elements with no valid selector.",
|
|
314
402
|
START_HIDDEN_SECTION: "\u{1F441} Widget visibility",
|
|
315
403
|
START_HIDDEN_TITLE: "Start hidden",
|
|
316
|
-
START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it."
|
|
404
|
+
START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it.",
|
|
405
|
+
RESUME_TTL_SECTION: "\u23F1 Recording continuity",
|
|
406
|
+
RESUME_TTL_LABEL: "Resume without asking (minutes)",
|
|
407
|
+
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.",
|
|
408
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Widget position",
|
|
409
|
+
WIDGET_POSITION_HINT: "Drag the record button to move it out of the way. Its position is remembered.",
|
|
410
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA Reset position",
|
|
411
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position reset"
|
|
317
412
|
},
|
|
318
413
|
SELECTOR_PICKER: {
|
|
319
414
|
TITLE: "Select a DOM element",
|
|
@@ -354,8 +449,10 @@ var I18N_EN = {
|
|
|
354
449
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Edit manually",
|
|
355
450
|
CLOSE_BTN: "\u2715 Close",
|
|
356
451
|
NEW_FILE_BTN: "+ New file",
|
|
452
|
+
NEW_FOLDER_BTN: "+ New folder",
|
|
357
453
|
REFRESH_BTN: "\u21BB Refresh",
|
|
358
454
|
NEW_FILE_PLACEHOLDER: "test-name",
|
|
455
|
+
NEW_FOLDER_PLACEHOLDER: "folder-name",
|
|
359
456
|
NEW_FILE_CONFIRM: "Create",
|
|
360
457
|
NEW_FILE_CANCEL: "Cancel"
|
|
361
458
|
},
|
|
@@ -368,10 +465,19 @@ var I18N_EN = {
|
|
|
368
465
|
NO_CHANGES: "No changes from original",
|
|
369
466
|
NO_CHANGES_SHORT: "No changes",
|
|
370
467
|
LAUNCH_BTN: "\u25B6 Run test",
|
|
468
|
+
LAUNCH_LOCAL_ONLY: "Move it to localhost to run tests",
|
|
469
|
+
LAUNCH_RUNNING: "\u23F3 Running\u2026",
|
|
470
|
+
LAUNCH_PASSED: "\u2713 Test passed",
|
|
471
|
+
LAUNCH_FAILED: "\u2717 Test failed",
|
|
472
|
+
LAUNCH_NO_RUNNER: "No local runner detected (did you start it?)",
|
|
371
473
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
372
474
|
SAVE_BTN: "\u{1F4BE} Save",
|
|
373
475
|
CLOSE_BTN: "\u2715 Close",
|
|
374
|
-
BACK_TO_EDITOR: "\u2190 Back to editor"
|
|
476
|
+
BACK_TO_EDITOR: "\u2190 Back to editor",
|
|
477
|
+
INSERT_BTN: "\u{1FA84} Insert blocks",
|
|
478
|
+
INSERT_TITLE: "Insert it() and beforeEach() into the editor content",
|
|
479
|
+
INSERT_DONE: "Blocks inserted into the editor",
|
|
480
|
+
INSERT_ERROR: "No valid describe() block found in the file"
|
|
375
481
|
},
|
|
376
482
|
RECORDER: {
|
|
377
483
|
FS_TITLE: "\u{1F4C1} File access",
|
|
@@ -400,7 +506,12 @@ var I18N_EN = {
|
|
|
400
506
|
BADGE_PAUSED: "\u23F8 PAUSED",
|
|
401
507
|
BADGE_REC: "\u25CF REC",
|
|
402
508
|
STOP_TITLE: "Stop (Ctrl+R)",
|
|
403
|
-
START_TITLE: "Record (Ctrl+R)"
|
|
509
|
+
START_TITLE: "Record (Ctrl+R)",
|
|
510
|
+
SESSION_RESUME_TITLE: "\u23FA Recording in progress",
|
|
511
|
+
SESSION_RESUME_TEXT: "There is an unfinished active recording.",
|
|
512
|
+
SESSION_RESUME_COUNT: "commands captured",
|
|
513
|
+
SESSION_CONTINUE_BTN: "Keep recording",
|
|
514
|
+
SESSION_DISCARD_BTN: "Discard"
|
|
404
515
|
}
|
|
405
516
|
};
|
|
406
517
|
|
|
@@ -438,8 +549,6 @@ var I18N_FR = {
|
|
|
438
549
|
COPY_DESCRIBE: "\u{1F5C2} Copier describe()",
|
|
439
550
|
MULTI_SELECT: "\u{1F5C2} Multi-s\xE9lection",
|
|
440
551
|
CANCEL_SELECT: "\u2715 Annuler",
|
|
441
|
-
SECTION_COMMANDS: "Commandes",
|
|
442
|
-
SECTION_INTERCEPTORS: "Intercepteurs",
|
|
443
552
|
COPY_CMDS_BTN: "\u{1F4CB} Copier les commandes",
|
|
444
553
|
COPY_ICPS_BTN: "\u{1F4CB} Copier les intercepteurs",
|
|
445
554
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
@@ -468,6 +577,18 @@ var I18N_FR = {
|
|
|
468
577
|
DATA_SECTION: "\u{1F4BE} Donn\xE9es",
|
|
469
578
|
DATA_DESC: "Exportez tous vos tests en JSON ou importez une sauvegarde.",
|
|
470
579
|
EXPORT_BTN: "\u2B06\uFE0F Exporter les tests",
|
|
580
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Exporter les tests",
|
|
581
|
+
EXPORT_MODE_ALL: "Tout",
|
|
582
|
+
EXPORT_MODE_MANUAL: "S\xE9lection manuelle",
|
|
583
|
+
EXPORT_MODE_TAGS: "Par tags",
|
|
584
|
+
EXPORT_ALL_DESC: "Tous les tests enregistr\xE9s seront export\xE9s.",
|
|
585
|
+
EXPORT_COUNT: "\xC0 exporter :",
|
|
586
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exporter",
|
|
587
|
+
EXPORT_CANCEL: "Annuler",
|
|
588
|
+
EXPORT_EMPTY: "Aucun test \xE0 exporter.",
|
|
589
|
+
EXPORT_NO_TAGS: "Aucun tag disponible.",
|
|
590
|
+
EXPORT_TAGS_HINT: "S\xE9lectionnez un ou plusieurs tags pour voir les tests qui seront export\xE9s.",
|
|
591
|
+
EXPORT_RESULT_LABEL: "Tests qui seront export\xE9s :",
|
|
471
592
|
IMPORT_BTN: "\u2B07\uFE0F Importer les tests",
|
|
472
593
|
FOLDER_UPDATED_TOAST: "\u2713 Dossier Cypress mis \xE0 jour",
|
|
473
594
|
FOLDER_ERROR_TOAST: "Erreur lors de l'acc\xE8s au dossier",
|
|
@@ -480,7 +601,14 @@ var I18N_FR = {
|
|
|
480
601
|
SMART_SELECTOR_SUB: "Affiche un s\xE9lecteur lors du clic sur des \xE9l\xE9ments sans s\xE9lecteur valide.",
|
|
481
602
|
START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE9 du widget",
|
|
482
603
|
START_HIDDEN_TITLE: "D\xE9marrer masqu\xE9",
|
|
483
|
-
START_HIDDEN_SUB: "Le widget d\xE9marre invisible. Utilisez Ctrl+Shift+E pour l'afficher ou le masquer."
|
|
604
|
+
START_HIDDEN_SUB: "Le widget d\xE9marre invisible. Utilisez Ctrl+Shift+E pour l'afficher ou le masquer.",
|
|
605
|
+
RESUME_TTL_SECTION: "\u23F1 Continuit\xE9 d'enregistrement",
|
|
606
|
+
RESUME_TTL_LABEL: "Reprendre sans demander (minutes)",
|
|
607
|
+
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.",
|
|
608
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Position du widget",
|
|
609
|
+
WIDGET_POSITION_HINT: "Fais glisser le bouton d'enregistrement pour le d\xE9placer. Sa position est m\xE9moris\xE9e.",
|
|
610
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA R\xE9initialiser la position",
|
|
611
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e"
|
|
484
612
|
},
|
|
485
613
|
SELECTOR_PICKER: {
|
|
486
614
|
TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
|
|
@@ -521,8 +649,10 @@ var I18N_FR = {
|
|
|
521
649
|
EDIT_MANUAL_BTN: "\u270F\uFE0F \xC9diter manuellement",
|
|
522
650
|
CLOSE_BTN: "\u2715 Fermer",
|
|
523
651
|
NEW_FILE_BTN: "+ Nouveau fichier",
|
|
652
|
+
NEW_FOLDER_BTN: "+ Nouveau dossier",
|
|
524
653
|
REFRESH_BTN: "\u21BB Actualiser",
|
|
525
654
|
NEW_FILE_PLACEHOLDER: "nom-du-test",
|
|
655
|
+
NEW_FOLDER_PLACEHOLDER: "nom-du-dossier",
|
|
526
656
|
NEW_FILE_CONFIRM: "Cr\xE9er",
|
|
527
657
|
NEW_FILE_CANCEL: "Annuler"
|
|
528
658
|
},
|
|
@@ -535,10 +665,19 @@ var I18N_FR = {
|
|
|
535
665
|
NO_CHANGES: "Aucun changement par rapport \xE0 l'original",
|
|
536
666
|
NO_CHANGES_SHORT: "Aucun changement",
|
|
537
667
|
LAUNCH_BTN: "\u25B6 Lancer le test",
|
|
668
|
+
LAUNCH_LOCAL_ONLY: "Passe en local pour pouvoir tester",
|
|
669
|
+
LAUNCH_RUNNING: "\u23F3 Ex\xE9cution\u2026",
|
|
670
|
+
LAUNCH_PASSED: "\u2713 Test r\xE9ussi",
|
|
671
|
+
LAUNCH_FAILED: "\u2717 Test \xE9chou\xE9",
|
|
672
|
+
LAUNCH_NO_RUNNER: "Aucun runner local d\xE9tect\xE9 (l'avez-vous d\xE9marr\xE9 ?)",
|
|
538
673
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
539
674
|
SAVE_BTN: "\u{1F4BE} Enregistrer",
|
|
540
675
|
CLOSE_BTN: "\u2715 Fermer",
|
|
541
|
-
BACK_TO_EDITOR: "\u2190 Retour \xE0 l'\xE9diteur"
|
|
676
|
+
BACK_TO_EDITOR: "\u2190 Retour \xE0 l'\xE9diteur",
|
|
677
|
+
INSERT_BTN: "\u{1FA84} Ins\xE9rer les blocs",
|
|
678
|
+
INSERT_TITLE: "Ins\xE9rer it() et beforeEach() dans le contenu de l'\xE9diteur",
|
|
679
|
+
INSERT_DONE: "Blocs ins\xE9r\xE9s dans l'\xE9diteur",
|
|
680
|
+
INSERT_ERROR: "Aucun bloc describe() valide trouv\xE9 dans le fichier"
|
|
542
681
|
},
|
|
543
682
|
RECORDER: {
|
|
544
683
|
FS_TITLE: "\u{1F4C1} Acc\xE8s aux fichiers",
|
|
@@ -567,7 +706,12 @@ var I18N_FR = {
|
|
|
567
706
|
BADGE_PAUSED: "\u23F8 PAUSE",
|
|
568
707
|
BADGE_REC: "\u25CF REC",
|
|
569
708
|
STOP_TITLE: "Arr\xEAter (Ctrl+R)",
|
|
570
|
-
START_TITLE: "Enregistrer (Ctrl+R)"
|
|
709
|
+
START_TITLE: "Enregistrer (Ctrl+R)",
|
|
710
|
+
SESSION_RESUME_TITLE: "\u23FA Enregistrement en cours",
|
|
711
|
+
SESSION_RESUME_TEXT: "Un enregistrement actif est inachev\xE9.",
|
|
712
|
+
SESSION_RESUME_COUNT: "commandes captur\xE9es",
|
|
713
|
+
SESSION_CONTINUE_BTN: "Continuer l'enregistrement",
|
|
714
|
+
SESSION_DISCARD_BTN: "Ignorer"
|
|
571
715
|
}
|
|
572
716
|
};
|
|
573
717
|
|
|
@@ -605,8 +749,6 @@ var I18N_IT = {
|
|
|
605
749
|
COPY_DESCRIBE: "\u{1F5C2} Copia describe()",
|
|
606
750
|
MULTI_SELECT: "\u{1F5C2} Multi-selezione",
|
|
607
751
|
CANCEL_SELECT: "\u2715 Annulla",
|
|
608
|
-
SECTION_COMMANDS: "Comandi",
|
|
609
|
-
SECTION_INTERCEPTORS: "Interceptor",
|
|
610
752
|
COPY_CMDS_BTN: "\u{1F4CB} Copia comandi",
|
|
611
753
|
COPY_ICPS_BTN: "\u{1F4CB} Copia interceptor",
|
|
612
754
|
DEFAULT_DESCRIBE: "Suite di test",
|
|
@@ -635,6 +777,18 @@ var I18N_IT = {
|
|
|
635
777
|
DATA_SECTION: "\u{1F4BE} Dati",
|
|
636
778
|
DATA_DESC: "Esporta tutti i tuoi test in JSON o importa un backup.",
|
|
637
779
|
EXPORT_BTN: "\u2B06\uFE0F Esporta test",
|
|
780
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Esporta test",
|
|
781
|
+
EXPORT_MODE_ALL: "Tutto",
|
|
782
|
+
EXPORT_MODE_MANUAL: "Selezione manuale",
|
|
783
|
+
EXPORT_MODE_TAGS: "Per tag",
|
|
784
|
+
EXPORT_ALL_DESC: "Verranno esportati tutti i test salvati.",
|
|
785
|
+
EXPORT_COUNT: "Da esportare:",
|
|
786
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Esporta",
|
|
787
|
+
EXPORT_CANCEL: "Annulla",
|
|
788
|
+
EXPORT_EMPTY: "Nessun test da esportare.",
|
|
789
|
+
EXPORT_NO_TAGS: "Nessun tag disponibile.",
|
|
790
|
+
EXPORT_TAGS_HINT: "Seleziona uno o pi\xF9 tag per vedere quali test verranno esportati.",
|
|
791
|
+
EXPORT_RESULT_LABEL: "Test che verranno esportati:",
|
|
638
792
|
IMPORT_BTN: "\u2B07\uFE0F Importa test",
|
|
639
793
|
FOLDER_UPDATED_TOAST: "\u2713 Cartella Cypress aggiornata",
|
|
640
794
|
FOLDER_ERROR_TOAST: "Errore durante l'accesso alla cartella",
|
|
@@ -647,7 +801,14 @@ var I18N_IT = {
|
|
|
647
801
|
SMART_SELECTOR_SUB: "Mostra un selettore quando si fa clic su elementi senza selettore valido.",
|
|
648
802
|
START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE0 del widget",
|
|
649
803
|
START_HIDDEN_TITLE: "Avvia nascosto",
|
|
650
|
-
START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo."
|
|
804
|
+
START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo.",
|
|
805
|
+
RESUME_TTL_SECTION: "\u23F1 Continuit\xE0 di registrazione",
|
|
806
|
+
RESUME_TTL_LABEL: "Riprendi senza chiedere (minuti)",
|
|
807
|
+
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.",
|
|
808
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Posizione del widget",
|
|
809
|
+
WIDGET_POSITION_HINT: "Trascina il pulsante di registrazione per spostarlo. La posizione viene ricordata.",
|
|
810
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA Reimposta posizione",
|
|
811
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata"
|
|
651
812
|
},
|
|
652
813
|
SELECTOR_PICKER: {
|
|
653
814
|
TITLE: "Seleziona un elemento DOM",
|
|
@@ -688,8 +849,10 @@ var I18N_IT = {
|
|
|
688
849
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Modifica manualmente",
|
|
689
850
|
CLOSE_BTN: "\u2715 Chiudi",
|
|
690
851
|
NEW_FILE_BTN: "+ Nuovo file",
|
|
852
|
+
NEW_FOLDER_BTN: "+ Nuova cartella",
|
|
691
853
|
REFRESH_BTN: "\u21BB Aggiorna",
|
|
692
854
|
NEW_FILE_PLACEHOLDER: "nome-del-test",
|
|
855
|
+
NEW_FOLDER_PLACEHOLDER: "nome-cartella",
|
|
693
856
|
NEW_FILE_CONFIRM: "Crea",
|
|
694
857
|
NEW_FILE_CANCEL: "Annulla"
|
|
695
858
|
},
|
|
@@ -702,10 +865,19 @@ var I18N_IT = {
|
|
|
702
865
|
NO_CHANGES: "Nessuna modifica rispetto all'originale",
|
|
703
866
|
NO_CHANGES_SHORT: "Nessuna modifica",
|
|
704
867
|
LAUNCH_BTN: "\u25B6 Esegui test",
|
|
868
|
+
LAUNCH_LOCAL_ONLY: "Spostati in locale per poter testare",
|
|
869
|
+
LAUNCH_RUNNING: "\u23F3 Esecuzione\u2026",
|
|
870
|
+
LAUNCH_PASSED: "\u2713 Test superato",
|
|
871
|
+
LAUNCH_FAILED: "\u2717 Test fallito",
|
|
872
|
+
LAUNCH_NO_RUNNER: "Nessun runner locale rilevato (l'hai avviato?)",
|
|
705
873
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
706
874
|
SAVE_BTN: "\u{1F4BE} Salva",
|
|
707
875
|
CLOSE_BTN: "\u2715 Chiudi",
|
|
708
|
-
BACK_TO_EDITOR: "\u2190 Torna all'editor"
|
|
876
|
+
BACK_TO_EDITOR: "\u2190 Torna all'editor",
|
|
877
|
+
INSERT_BTN: "\u{1FA84} Inserisci blocchi",
|
|
878
|
+
INSERT_TITLE: "Inserisci it() e beforeEach() nel contenuto dell'editor",
|
|
879
|
+
INSERT_DONE: "Blocchi inseriti nell'editor",
|
|
880
|
+
INSERT_ERROR: "Nessun blocco describe() valido trovato nel file"
|
|
709
881
|
},
|
|
710
882
|
RECORDER: {
|
|
711
883
|
FS_TITLE: "\u{1F4C1} Accesso ai file",
|
|
@@ -734,7 +906,12 @@ var I18N_IT = {
|
|
|
734
906
|
BADGE_PAUSED: "\u23F8 PAUSA",
|
|
735
907
|
BADGE_REC: "\u25CF REC",
|
|
736
908
|
STOP_TITLE: "Ferma (Ctrl+R)",
|
|
737
|
-
START_TITLE: "Registra (Ctrl+R)"
|
|
909
|
+
START_TITLE: "Registra (Ctrl+R)",
|
|
910
|
+
SESSION_RESUME_TITLE: "\u23FA Registrazione in corso",
|
|
911
|
+
SESSION_RESUME_TEXT: "C'\xE8 una registrazione attiva non terminata.",
|
|
912
|
+
SESSION_RESUME_COUNT: "comandi catturati",
|
|
913
|
+
SESSION_CONTINUE_BTN: "Continua a registrare",
|
|
914
|
+
SESSION_DISCARD_BTN: "Annulla"
|
|
738
915
|
}
|
|
739
916
|
};
|
|
740
917
|
|
|
@@ -772,8 +949,6 @@ var I18N_DE = {
|
|
|
772
949
|
COPY_DESCRIBE: "\u{1F5C2} describe() kopieren",
|
|
773
950
|
MULTI_SELECT: "\u{1F5C2} Mehrfachauswahl",
|
|
774
951
|
CANCEL_SELECT: "\u2715 Abbrechen",
|
|
775
|
-
SECTION_COMMANDS: "Befehle",
|
|
776
|
-
SECTION_INTERCEPTORS: "Interceptors",
|
|
777
952
|
COPY_CMDS_BTN: "\u{1F4CB} Befehle kopieren",
|
|
778
953
|
COPY_ICPS_BTN: "\u{1F4CB} Interceptors kopieren",
|
|
779
954
|
DEFAULT_DESCRIBE: "Test-Suite",
|
|
@@ -802,6 +977,18 @@ var I18N_DE = {
|
|
|
802
977
|
DATA_SECTION: "\u{1F4BE} Daten",
|
|
803
978
|
DATA_DESC: "Exportieren Sie alle Tests als JSON oder importieren Sie ein Backup.",
|
|
804
979
|
EXPORT_BTN: "\u2B06\uFE0F Tests exportieren",
|
|
980
|
+
EXPORT_DIALOG_TITLE: "\u2B06\uFE0F Tests exportieren",
|
|
981
|
+
EXPORT_MODE_ALL: "Alle",
|
|
982
|
+
EXPORT_MODE_MANUAL: "Manuelle Auswahl",
|
|
983
|
+
EXPORT_MODE_TAGS: "Nach Tags",
|
|
984
|
+
EXPORT_ALL_DESC: "Alle gespeicherten Tests werden exportiert.",
|
|
985
|
+
EXPORT_COUNT: "Zu exportieren:",
|
|
986
|
+
EXPORT_CONFIRM: "\u2B06\uFE0F Exportieren",
|
|
987
|
+
EXPORT_CANCEL: "Abbrechen",
|
|
988
|
+
EXPORT_EMPTY: "Keine Tests zum Exportieren.",
|
|
989
|
+
EXPORT_NO_TAGS: "Keine Tags verf\xFCgbar.",
|
|
990
|
+
EXPORT_TAGS_HINT: "W\xE4hle ein oder mehrere Tags, um zu sehen, welche Tests exportiert werden.",
|
|
991
|
+
EXPORT_RESULT_LABEL: "Diese Tests werden exportiert:",
|
|
805
992
|
IMPORT_BTN: "\u2B07\uFE0F Tests importieren",
|
|
806
993
|
FOLDER_UPDATED_TOAST: "\u2713 Cypress-Ordner aktualisiert",
|
|
807
994
|
FOLDER_ERROR_TOAST: "Fehler beim Zugriff auf den Ordner",
|
|
@@ -814,7 +1001,14 @@ var I18N_DE = {
|
|
|
814
1001
|
SMART_SELECTOR_SUB: "Zeigt ein Auswahlfeld, wenn auf Elemente ohne g\xFCltigen Selektor geklickt wird.",
|
|
815
1002
|
START_HIDDEN_SECTION: "\u{1F441} Widget-Sichtbarkeit",
|
|
816
1003
|
START_HIDDEN_TITLE: "Versteckt starten",
|
|
817
|
-
START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden."
|
|
1004
|
+
START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden.",
|
|
1005
|
+
RESUME_TTL_SECTION: "\u23F1 Aufzeichnungskontinuit\xE4t",
|
|
1006
|
+
RESUME_TTL_LABEL: "Ohne Nachfrage fortsetzen (Minuten)",
|
|
1007
|
+
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.",
|
|
1008
|
+
WIDGET_POSITION_SECTION: "\u{1F9F2} Widget-Position",
|
|
1009
|
+
WIDGET_POSITION_HINT: "Ziehe den Aufnahme-Button, um ihn aus dem Weg zu schieben. Die Position wird gespeichert.",
|
|
1010
|
+
WIDGET_POSITION_RESET_BTN: "\u21BA Position zur\xFCcksetzen",
|
|
1011
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt"
|
|
818
1012
|
},
|
|
819
1013
|
SELECTOR_PICKER: {
|
|
820
1014
|
TITLE: "DOM-Element ausw\xE4hlen",
|
|
@@ -855,8 +1049,10 @@ var I18N_DE = {
|
|
|
855
1049
|
EDIT_MANUAL_BTN: "\u270F\uFE0F Manuell bearbeiten",
|
|
856
1050
|
CLOSE_BTN: "\u2715 Schlie\xDFen",
|
|
857
1051
|
NEW_FILE_BTN: "+ Neue Datei",
|
|
1052
|
+
NEW_FOLDER_BTN: "+ Neuer Ordner",
|
|
858
1053
|
REFRESH_BTN: "\u21BB Aktualisieren",
|
|
859
1054
|
NEW_FILE_PLACEHOLDER: "test-name",
|
|
1055
|
+
NEW_FOLDER_PLACEHOLDER: "ordnername",
|
|
860
1056
|
NEW_FILE_CONFIRM: "Erstellen",
|
|
861
1057
|
NEW_FILE_CANCEL: "Abbrechen"
|
|
862
1058
|
},
|
|
@@ -869,10 +1065,19 @@ var I18N_DE = {
|
|
|
869
1065
|
NO_CHANGES: "Keine \xC4nderungen gegen\xFCber dem Original",
|
|
870
1066
|
NO_CHANGES_SHORT: "Keine \xC4nderungen",
|
|
871
1067
|
LAUNCH_BTN: "\u25B6 Test starten",
|
|
1068
|
+
LAUNCH_LOCAL_ONLY: "Wechsle zu localhost, um Tests auszuf\xFChren",
|
|
1069
|
+
LAUNCH_RUNNING: "\u23F3 L\xE4uft\u2026",
|
|
1070
|
+
LAUNCH_PASSED: "\u2713 Test bestanden",
|
|
1071
|
+
LAUNCH_FAILED: "\u2717 Test fehlgeschlagen",
|
|
1072
|
+
LAUNCH_NO_RUNNER: "Kein lokaler Runner erkannt (gestartet?)",
|
|
872
1073
|
DIFF_BTN: "\u{1F4CA} Diff",
|
|
873
1074
|
SAVE_BTN: "\u{1F4BE} Speichern",
|
|
874
1075
|
CLOSE_BTN: "\u2715 Schlie\xDFen",
|
|
875
|
-
BACK_TO_EDITOR: "\u2190 Zur\xFCck zum Editor"
|
|
1076
|
+
BACK_TO_EDITOR: "\u2190 Zur\xFCck zum Editor",
|
|
1077
|
+
INSERT_BTN: "\u{1FA84} Bl\xF6cke einf\xFCgen",
|
|
1078
|
+
INSERT_TITLE: "it() und beforeEach() in den Editor-Inhalt einf\xFCgen",
|
|
1079
|
+
INSERT_DONE: "Bl\xF6cke in den Editor eingef\xFCgt",
|
|
1080
|
+
INSERT_ERROR: "Kein g\xFCltiger describe()-Block in der Datei gefunden"
|
|
876
1081
|
},
|
|
877
1082
|
RECORDER: {
|
|
878
1083
|
FS_TITLE: "\u{1F4C1} Dateizugriff",
|
|
@@ -901,13 +1106,18 @@ var I18N_DE = {
|
|
|
901
1106
|
BADGE_PAUSED: "\u23F8 PAUSE",
|
|
902
1107
|
BADGE_REC: "\u25CF REC",
|
|
903
1108
|
STOP_TITLE: "Stopp (Ctrl+R)",
|
|
904
|
-
START_TITLE: "Aufzeichnen (Ctrl+R)"
|
|
1109
|
+
START_TITLE: "Aufzeichnen (Ctrl+R)",
|
|
1110
|
+
SESSION_RESUME_TITLE: "\u23FA Aufzeichnung l\xE4uft",
|
|
1111
|
+
SESSION_RESUME_TEXT: "Es gibt eine nicht abgeschlossene aktive Aufzeichnung.",
|
|
1112
|
+
SESSION_RESUME_COUNT: "erfasste Befehle",
|
|
1113
|
+
SESSION_CONTINUE_BTN: "Weiter aufzeichnen",
|
|
1114
|
+
SESSION_DISCARD_BTN: "Verwerfen"
|
|
905
1115
|
}
|
|
906
1116
|
};
|
|
907
1117
|
|
|
908
1118
|
// src/services/translation.service.ts
|
|
909
1119
|
var TranslationService = class {
|
|
910
|
-
lang
|
|
1120
|
+
lang$;
|
|
911
1121
|
translations = {
|
|
912
1122
|
es: I18N_ES,
|
|
913
1123
|
en: I18N_EN,
|
|
@@ -916,17 +1126,21 @@ var TranslationService = class {
|
|
|
916
1126
|
de: I18N_DE
|
|
917
1127
|
};
|
|
918
1128
|
constructor() {
|
|
919
|
-
this.lang = this.detectLang();
|
|
1129
|
+
this.lang$ = new Subject(this.detectLang());
|
|
920
1130
|
}
|
|
921
1131
|
setLang(lang) {
|
|
922
|
-
this.lang
|
|
1132
|
+
this.lang$.next(lang);
|
|
923
1133
|
}
|
|
924
1134
|
getLang() {
|
|
925
|
-
return this.lang;
|
|
1135
|
+
return this.lang$.getValue();
|
|
1136
|
+
}
|
|
1137
|
+
/** Subscribe to language changes; returns an unsubscribe function. */
|
|
1138
|
+
onLangChange(fn) {
|
|
1139
|
+
return this.lang$.subscribe(fn);
|
|
926
1140
|
}
|
|
927
1141
|
translate(key) {
|
|
928
1142
|
const keys = key.split(".");
|
|
929
|
-
let value = this.translations[this.lang];
|
|
1143
|
+
let value = this.translations[this.lang$.getValue()];
|
|
930
1144
|
for (const k of keys) {
|
|
931
1145
|
value = value?.[k];
|
|
932
1146
|
if (value === void 0) return key;
|
|
@@ -949,6 +1163,9 @@ function gcd(a, b) {
|
|
|
949
1163
|
}
|
|
950
1164
|
return a;
|
|
951
1165
|
}
|
|
1166
|
+
function escapeSingleQuotes(value) {
|
|
1167
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1168
|
+
}
|
|
952
1169
|
function normalizeBlock(code, baseIndent) {
|
|
953
1170
|
const lines = code.split("\n").map((l) => l.trimEnd());
|
|
954
1171
|
const nonEmpty = lines.filter((l) => l.trim().length > 0);
|
|
@@ -971,7 +1188,7 @@ var TransformationService = class {
|
|
|
971
1188
|
}
|
|
972
1189
|
generateItDescription(description, commands) {
|
|
973
1190
|
const commandsBlock = commands.map((cmd) => normalizeBlock(cmd, " ")).join("\n");
|
|
974
|
-
return `it('${description}', () => {
|
|
1191
|
+
return `it('${escapeSingleQuotes(description)}', () => {
|
|
975
1192
|
${commandsBlock}
|
|
976
1193
|
});`;
|
|
977
1194
|
}
|
|
@@ -1025,28 +1242,14 @@ ${lines}
|
|
|
1025
1242
|
};
|
|
1026
1243
|
var advancedTestTransformationService = new AdvancedTestTransformationService();
|
|
1027
1244
|
|
|
1028
|
-
// src/utils/subject.ts
|
|
1029
|
-
var Subject = class {
|
|
1030
|
-
_value;
|
|
1031
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1032
|
-
constructor(initialValue) {
|
|
1033
|
-
this._value = initialValue;
|
|
1034
|
-
}
|
|
1035
|
-
next(value) {
|
|
1036
|
-
this._value = value;
|
|
1037
|
-
this.listeners.forEach((l) => l(value));
|
|
1038
|
-
}
|
|
1039
|
-
getValue() {
|
|
1040
|
-
return this._value;
|
|
1041
|
-
}
|
|
1042
|
-
subscribe(fn) {
|
|
1043
|
-
this.listeners.add(fn);
|
|
1044
|
-
return () => this.listeners.delete(fn);
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
1245
|
// src/services/recording.service.ts
|
|
1049
1246
|
var OWN_SELECTOR = '[data-cy="lib-e2e-cypress-for-dummys"]';
|
|
1247
|
+
function createSessionId() {
|
|
1248
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1249
|
+
return crypto.randomUUID();
|
|
1250
|
+
}
|
|
1251
|
+
return `sess-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;
|
|
1252
|
+
}
|
|
1050
1253
|
var RecordingService = class {
|
|
1051
1254
|
commands$ = new Subject([]);
|
|
1052
1255
|
interceptors$ = new Subject([]);
|
|
@@ -1056,6 +1259,9 @@ var RecordingService = class {
|
|
|
1056
1259
|
inputDebounceTimers = /* @__PURE__ */ new Map();
|
|
1057
1260
|
abort = new AbortController();
|
|
1058
1261
|
selectorStrategy = "data-cy";
|
|
1262
|
+
/** Stable id of the current live session (null when none has started). */
|
|
1263
|
+
sessionId = null;
|
|
1264
|
+
startedAt = 0;
|
|
1059
1265
|
// Stored originals for history patching cleanup
|
|
1060
1266
|
origPushState = history.pushState.bind(history);
|
|
1061
1267
|
origReplaceState = history.replaceState.bind(history);
|
|
@@ -1067,12 +1273,42 @@ var RecordingService = class {
|
|
|
1067
1273
|
}
|
|
1068
1274
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
1069
1275
|
startRecording() {
|
|
1276
|
+
this.sessionId = createSessionId();
|
|
1277
|
+
this.startedAt = Date.now();
|
|
1070
1278
|
this.isPaused$.next(false);
|
|
1071
1279
|
this.isRecording$.next(true);
|
|
1072
1280
|
this.addCommand(`cy.viewport(1900, 1200)`);
|
|
1073
1281
|
this.addCommand(`cy.visit('${window.location.pathname}')`);
|
|
1074
1282
|
this.addCommand(`cy.get('[data-cy="lib-e2e-cypress-for-dummys"]').invoke('hide');`);
|
|
1075
1283
|
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Rehydrates a previously persisted session WITHOUT running the startRecording
|
|
1286
|
+
* bootstrap (no viewport/visit/hide). Used to continue a recording across a
|
|
1287
|
+
* micro-frontend crossing or a same-origin reload.
|
|
1288
|
+
* See docs/specs/006-cross-app-recording-continuity.md.
|
|
1289
|
+
*/
|
|
1290
|
+
restoreSession(state) {
|
|
1291
|
+
this.sessionId = state.sessionId;
|
|
1292
|
+
this.startedAt = state.startedAt;
|
|
1293
|
+
this.selectorStrategy = state.selectorStrategy;
|
|
1294
|
+
this.commands$.next([...state.commands]);
|
|
1295
|
+
this.interceptors$.next([...state.interceptors]);
|
|
1296
|
+
this.isPaused$.next(state.isPaused);
|
|
1297
|
+
this.isRecording$.next(state.isRecording);
|
|
1298
|
+
}
|
|
1299
|
+
/** Full snapshot of the live session for persistence. */
|
|
1300
|
+
getSessionSnapshot() {
|
|
1301
|
+
return {
|
|
1302
|
+
sessionId: this.sessionId ?? createSessionId(),
|
|
1303
|
+
isRecording: this.isRecording$.getValue(),
|
|
1304
|
+
isPaused: this.isPaused$.getValue(),
|
|
1305
|
+
commands: this.commands$.getValue(),
|
|
1306
|
+
interceptors: this.interceptors$.getValue(),
|
|
1307
|
+
selectorStrategy: this.selectorStrategy,
|
|
1308
|
+
startedAt: this.startedAt,
|
|
1309
|
+
updatedAt: Date.now()
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1076
1312
|
stopRecording() {
|
|
1077
1313
|
this.isPaused$.next(false);
|
|
1078
1314
|
this.isRecording$.next(false);
|
|
@@ -1159,6 +1395,21 @@ var RecordingService = class {
|
|
|
1159
1395
|
onPauseChange(fn) {
|
|
1160
1396
|
return this.isPaused$.subscribe(fn);
|
|
1161
1397
|
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Fires a full session snapshot whenever any persisted field changes
|
|
1400
|
+
* (commands, interceptors, recording or paused state). Drives the debounced
|
|
1401
|
+
* persistence of the live session. Returns a combined unsubscribe.
|
|
1402
|
+
*/
|
|
1403
|
+
onSessionChange(fn) {
|
|
1404
|
+
const emit = () => fn(this.getSessionSnapshot());
|
|
1405
|
+
const unsubs = [
|
|
1406
|
+
this.commands$.subscribe(emit),
|
|
1407
|
+
this.interceptors$.subscribe(emit),
|
|
1408
|
+
this.isRecording$.subscribe(emit),
|
|
1409
|
+
this.isPaused$.subscribe(emit)
|
|
1410
|
+
];
|
|
1411
|
+
return () => unsubs.forEach((u) => u());
|
|
1412
|
+
}
|
|
1162
1413
|
onSelectorNotFound(fn) {
|
|
1163
1414
|
return this.selectorNotFound$.subscribe((v) => {
|
|
1164
1415
|
if (v) fn(v.target, v.action);
|
|
@@ -1390,6 +1641,7 @@ var RecordingService = class {
|
|
|
1390
1641
|
|
|
1391
1642
|
// src/services/persistence.service.ts
|
|
1392
1643
|
import { openDB } from "idb";
|
|
1644
|
+
var ACTIVE_SESSION_ID = 1;
|
|
1393
1645
|
var PersistenceService = class {
|
|
1394
1646
|
// dbName is overridable so tests can use unique names for isolation.
|
|
1395
1647
|
constructor(dbName = DB_SCHEMA.name) {
|
|
@@ -1443,7 +1695,7 @@ var PersistenceService = class {
|
|
|
1443
1695
|
if (!record) return null;
|
|
1444
1696
|
const commands = await this.getCommandStrings(testId);
|
|
1445
1697
|
const interceptors = await this.getInterceptorStrings(testId);
|
|
1446
|
-
const itBlock = `it('${record.name}', () => {
|
|
1698
|
+
const itBlock = `it('${escapeSingleQuotes(record.name)}', () => {
|
|
1447
1699
|
${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
1448
1700
|
});`;
|
|
1449
1701
|
const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
|
|
@@ -1524,6 +1776,25 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
1524
1776
|
const records = await db.getAll("configuration");
|
|
1525
1777
|
return records[0] ?? null;
|
|
1526
1778
|
}
|
|
1779
|
+
// ── Active recording session ──────────────────────────────────────────────
|
|
1780
|
+
/** Upserts the live recording session (single record, fixed key). */
|
|
1781
|
+
async saveActiveSession(state) {
|
|
1782
|
+
const db = await this.getDB();
|
|
1783
|
+
await db.put("activeSession", { ...state, id: ACTIVE_SESSION_ID });
|
|
1784
|
+
}
|
|
1785
|
+
/** Returns the persisted live session, or null when none is stored. */
|
|
1786
|
+
async getActiveSession() {
|
|
1787
|
+
const db = await this.getDB();
|
|
1788
|
+
const record = await db.get("activeSession", ACTIVE_SESSION_ID);
|
|
1789
|
+
if (!record) return null;
|
|
1790
|
+
const { id: _id, ...state } = record;
|
|
1791
|
+
return state;
|
|
1792
|
+
}
|
|
1793
|
+
/** Removes the live session record. Safe to call when none exists. */
|
|
1794
|
+
async clearActiveSession() {
|
|
1795
|
+
const db = await this.getDB();
|
|
1796
|
+
await db.delete("activeSession", ACTIVE_SESSION_ID);
|
|
1797
|
+
}
|
|
1527
1798
|
// ── Bulk operations ───────────────────────────────────────────────────────
|
|
1528
1799
|
async clearAllData() {
|
|
1529
1800
|
const db = await this.getDB();
|
|
@@ -1534,10 +1805,18 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
1534
1805
|
]);
|
|
1535
1806
|
}
|
|
1536
1807
|
async ingestFileData(tests, interceptors) {
|
|
1537
|
-
|
|
1538
|
-
this.
|
|
1539
|
-
|
|
1540
|
-
|
|
1808
|
+
if (Array.isArray(tests)) {
|
|
1809
|
+
const db = await this.getDB();
|
|
1810
|
+
for (const test of tests) {
|
|
1811
|
+
const { id: _id, commands, interceptors: testInterceptors, ...record } = test;
|
|
1812
|
+
const newId = await db.add("tests", record);
|
|
1813
|
+
const cmds = Array.isArray(commands) ? commands : [];
|
|
1814
|
+
const icps = Array.isArray(testInterceptors) ? testInterceptors : [];
|
|
1815
|
+
if (cmds.length) await this.insertCommands(cmds, newId);
|
|
1816
|
+
if (icps.length) await this.insertInterceptors(icps, newId);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
await this.bulkInsertWithoutId("interceptors", interceptors);
|
|
1541
1820
|
}
|
|
1542
1821
|
async requestDirectoryPermissions() {
|
|
1543
1822
|
if (!("showDirectoryPicker" in window)) {
|
|
@@ -1980,6 +2259,56 @@ function showToast(message, isSuccess = true) {
|
|
|
1980
2259
|
setTimeout(() => toast.remove(), 3e3);
|
|
1981
2260
|
}
|
|
1982
2261
|
|
|
2262
|
+
// src/utils/export-selection.utils.ts
|
|
2263
|
+
function selectTestsForExport(tests, mode, opts = {}) {
|
|
2264
|
+
if (mode === "all") return [...tests];
|
|
2265
|
+
if (mode === "manual") {
|
|
2266
|
+
const ids = new Set(opts.ids ?? []);
|
|
2267
|
+
return tests.filter((t) => ids.has(t.id));
|
|
2268
|
+
}
|
|
2269
|
+
const tags = new Set(opts.tags ?? []);
|
|
2270
|
+
if (tags.size === 0) return [];
|
|
2271
|
+
return tests.filter((t) => (t.tags ?? []).some((tag) => tags.has(tag)));
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
// src/utils/host.utils.ts
|
|
2275
|
+
function isLocalHost(hostname) {
|
|
2276
|
+
if (!hostname) return true;
|
|
2277
|
+
const h = hostname.toLowerCase();
|
|
2278
|
+
return h === "localhost" || h.endsWith(".localhost") || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0";
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// src/utils/widget-position.utils.ts
|
|
2282
|
+
var TOGGLE_MARGIN = 30;
|
|
2283
|
+
var DRAG_THRESHOLD = 5;
|
|
2284
|
+
var DEFAULT_EDGE_OFFSET = 46;
|
|
2285
|
+
function boxOffsetForDirection(dir) {
|
|
2286
|
+
return {
|
|
2287
|
+
x: dir.endsWith("left") ? 144 : 46,
|
|
2288
|
+
y: dir.startsWith("up") ? 144 : 46
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
function clampTogglePosition(x, y, vw, vh, margin = TOGGLE_MARGIN) {
|
|
2292
|
+
const maxX = Math.max(margin, vw - margin);
|
|
2293
|
+
const maxY = Math.max(margin, vh - margin);
|
|
2294
|
+
return {
|
|
2295
|
+
x: Math.min(Math.max(x, margin), maxX),
|
|
2296
|
+
y: Math.min(Math.max(y, margin), maxY)
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
function resolveExpandDirection(x, y, vw, vh) {
|
|
2300
|
+
const vertical = y < vh / 2 ? "down" : "up";
|
|
2301
|
+
const horizontal = x < vw / 2 ? "right" : "left";
|
|
2302
|
+
return `${vertical}-${horizontal}`;
|
|
2303
|
+
}
|
|
2304
|
+
function defaultTogglePosition(vw, vh) {
|
|
2305
|
+
return { x: vw - DEFAULT_EDGE_OFFSET, y: vh - DEFAULT_EDGE_OFFSET };
|
|
2306
|
+
}
|
|
2307
|
+
function boxTopLeftFor(toggleCentre, dir) {
|
|
2308
|
+
const off = boxOffsetForDirection(dir);
|
|
2309
|
+
return { x: toggleCentre.x - off.x, y: toggleCentre.y - off.y };
|
|
2310
|
+
}
|
|
2311
|
+
|
|
1983
2312
|
// src/components/test-previsualizer/test-previsualizer.styles.ts
|
|
1984
2313
|
var TEST_PREVISUALIZER_STYLES = `
|
|
1985
2314
|
:host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
|
|
@@ -2576,7 +2905,7 @@ function highlightLine(line) {
|
|
|
2576
2905
|
|
|
2577
2906
|
// src/components/test-editor/test-editor.template.ts
|
|
2578
2907
|
function renderTestEditor(state, t) {
|
|
2579
|
-
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest } = state;
|
|
2908
|
+
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest, locale } = state;
|
|
2580
2909
|
const tagFilterHtml = tags.length ? `<div class="tag-filter">
|
|
2581
2910
|
${tags.map((tag) => `<button class="tag-chip${activeTag === tag ? " active" : ""}" data-filter-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}
|
|
2582
2911
|
</div>` : `<div class="tag-filter" style="color:#484f58;font-size:11px">${t("TEST_EDITOR.NO_TAGS")}</div>`;
|
|
@@ -2587,12 +2916,12 @@ function renderTestEditor(state, t) {
|
|
|
2587
2916
|
</div>` : "";
|
|
2588
2917
|
const rows = visible.map((test, i) => {
|
|
2589
2918
|
const expanded = expandedIndex === i;
|
|
2590
|
-
const date = new Date(test.createdAt).toLocaleString(
|
|
2919
|
+
const date = new Date(test.createdAt).toLocaleString(locale, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
2591
2920
|
const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
|
|
2592
2921
|
const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
|
|
2593
2922
|
const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
|
|
2594
2923
|
const hasIcps = (test.interceptors ?? []).length > 0;
|
|
2595
|
-
const itBlockCode = `it('${test.name}', () => {
|
|
2924
|
+
const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
|
|
2596
2925
|
${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
2597
2926
|
});`;
|
|
2598
2927
|
const icpBlockCode = icps.length ? `beforeEach(() => {
|
|
@@ -2700,11 +3029,11 @@ var TestEditorElement = class extends HTMLElement {
|
|
|
2700
3029
|
` : "";
|
|
2701
3030
|
const itBlocks = selected.map((t) => {
|
|
2702
3031
|
const cmds = (t.commands ?? []).map((c) => ` ${c}`).join("\n");
|
|
2703
|
-
return ` it('${(t.name ?? "")
|
|
3032
|
+
return ` it('${escapeSingleQuotes(t.name ?? "")}', () => {
|
|
2704
3033
|
${cmds}
|
|
2705
3034
|
});`;
|
|
2706
3035
|
}).join("\n\n");
|
|
2707
|
-
const block = `describe('${name
|
|
3036
|
+
const block = `describe('${escapeSingleQuotes(name)}', () => {
|
|
2708
3037
|
${beforeEach}${itBlocks}
|
|
2709
3038
|
});`;
|
|
2710
3039
|
navigator.clipboard?.writeText(block);
|
|
@@ -2738,7 +3067,8 @@ ${beforeEach}${itBlocks}
|
|
|
2738
3067
|
selectedIds: this.selectedIds,
|
|
2739
3068
|
describeName: this.describeName,
|
|
2740
3069
|
expandedIndex: this.expandedIndex,
|
|
2741
|
-
interceptorsByTest: this.interceptorsByTest
|
|
3070
|
+
interceptorsByTest: this.interceptorsByTest,
|
|
3071
|
+
locale: localeForLang(this.translation.getLang())
|
|
2742
3072
|
}, this.t.bind(this))}`;
|
|
2743
3073
|
this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
|
|
2744
3074
|
this.shadow.querySelectorAll("[data-filter-tag]").forEach((el) => {
|
|
@@ -2895,6 +3225,71 @@ var CONFIGURATION_STYLES = `
|
|
|
2895
3225
|
|
|
2896
3226
|
/* \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 */
|
|
2897
3227
|
.data-desc { font-size: 11px; color: #484f58; margin-bottom: 10px; line-height: 1.5; }
|
|
3228
|
+
|
|
3229
|
+
/* \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 */
|
|
3230
|
+
.export-overlay {
|
|
3231
|
+
position: fixed; inset: 0; z-index: 100000;
|
|
3232
|
+
background: rgba(1,4,9,0.7);
|
|
3233
|
+
display: flex; align-items: center; justify-content: center; padding: 20px;
|
|
3234
|
+
}
|
|
3235
|
+
.export-modal {
|
|
3236
|
+
width: 640px; max-width: 100%; max-height: 86vh;
|
|
3237
|
+
display: flex; flex-direction: column;
|
|
3238
|
+
background: #161b22; border: 1px solid #30363d; border-radius: 12px;
|
|
3239
|
+
box-shadow: 0 12px 40px rgba(0,0,0,0.5); overflow: hidden;
|
|
3240
|
+
}
|
|
3241
|
+
.export-hd {
|
|
3242
|
+
padding: 14px 18px; font-size: 13px; font-weight: 700;
|
|
3243
|
+
border-bottom: 1px solid #21262d; color: #e6edf3;
|
|
3244
|
+
}
|
|
3245
|
+
.export-modes { display: flex; gap: 6px; padding: 12px 18px 0; }
|
|
3246
|
+
.export-mode {
|
|
3247
|
+
flex: 1; padding: 7px 10px; font-size: 11px; border-radius: 6px;
|
|
3248
|
+
background: #0d1117; border: 1px solid #30363d; color: #8b949e;
|
|
3249
|
+
}
|
|
3250
|
+
.export-mode:hover { background: #21262d; color: #e6edf3; }
|
|
3251
|
+
.export-mode.active { background: rgba(47,129,247,0.15); border-color: #2f81f7; color: #2f81f7; }
|
|
3252
|
+
.export-body {
|
|
3253
|
+
padding: 14px 18px; overflow-y: auto; flex: 1; min-height: 160px;
|
|
3254
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
3255
|
+
}
|
|
3256
|
+
.export-tag-hint { font-size: 12px; color: #484f58; margin-top: 12px; }
|
|
3257
|
+
.export-result-label {
|
|
3258
|
+
font-size: 10px; color: #484f58; text-transform: uppercase; letter-spacing: 0.6px;
|
|
3259
|
+
font-weight: 600; margin: 14px 0 6px;
|
|
3260
|
+
}
|
|
3261
|
+
.export-row-static { cursor: default; }
|
|
3262
|
+
.export-row-static:hover { background: transparent; }
|
|
3263
|
+
.export-all-desc { font-size: 12px; color: #8b949e; }
|
|
3264
|
+
.export-empty { font-size: 12px; color: #484f58; text-align: center; padding: 20px; }
|
|
3265
|
+
.export-list { display: flex; flex-direction: column; gap: 4px; }
|
|
3266
|
+
.export-row {
|
|
3267
|
+
display: flex; align-items: center; gap: 8px;
|
|
3268
|
+
padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 12px; color: #c9d1d9;
|
|
3269
|
+
}
|
|
3270
|
+
.export-row:hover { background: #0d1117; }
|
|
3271
|
+
.export-row-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3272
|
+
.export-row-tag {
|
|
3273
|
+
font-size: 9px; color: #8b949e; background: #0d1117;
|
|
3274
|
+
border: 1px solid #30363d; border-radius: 10px; padding: 1px 7px; flex-shrink: 0;
|
|
3275
|
+
}
|
|
3276
|
+
.export-tags { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
3277
|
+
.export-tag {
|
|
3278
|
+
padding: 4px 10px; font-size: 11px; border-radius: 12px;
|
|
3279
|
+
background: #0d1117; border: 1px solid #30363d; color: #8b949e;
|
|
3280
|
+
}
|
|
3281
|
+
.export-tag:hover { background: #21262d; color: #e6edf3; }
|
|
3282
|
+
.export-tag.active { background: rgba(47,129,247,0.15); border-color: #2f81f7; color: #2f81f7; }
|
|
3283
|
+
.export-ft {
|
|
3284
|
+
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
|
3285
|
+
padding: 12px 18px; border-top: 1px solid #21262d;
|
|
3286
|
+
}
|
|
3287
|
+
.export-count { font-size: 11px; color: #8b949e; }
|
|
3288
|
+
.export-count b { color: #e6edf3; }
|
|
3289
|
+
.export-ft-actions { display: flex; gap: 8px; }
|
|
3290
|
+
.btn-export-confirm { background: #238636; border-color: #238636; color: #fff; }
|
|
3291
|
+
.btn-export-confirm:hover:not(:disabled) { background: #2ea043; border-color: #2ea043; }
|
|
3292
|
+
.btn-export-confirm:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
2898
3293
|
`;
|
|
2899
3294
|
|
|
2900
3295
|
// src/components/configuration/configuration.template.ts
|
|
@@ -2905,8 +3300,64 @@ var LANGS = [
|
|
|
2905
3300
|
{ value: "it", label: "Italiano" },
|
|
2906
3301
|
{ value: "de", label: "Deutsch" }
|
|
2907
3302
|
];
|
|
3303
|
+
function renderExportOverlay(state, t) {
|
|
3304
|
+
const { isExporting, exportMode, exportTests, exportSelectedIds, exportSelectedTags } = state;
|
|
3305
|
+
if (!isExporting) return "";
|
|
3306
|
+
const count = selectTestsForExport(exportTests, exportMode, {
|
|
3307
|
+
ids: exportSelectedIds,
|
|
3308
|
+
tags: exportSelectedTags
|
|
3309
|
+
}).length;
|
|
3310
|
+
const modeBtn = (mode, key) => `<button class="export-mode ${exportMode === mode ? "active" : ""}" data-export-mode="${mode}">${t(key)}</button>`;
|
|
3311
|
+
let body;
|
|
3312
|
+
if (exportTests.length === 0) {
|
|
3313
|
+
body = `<div class="export-empty">${t("CONFIG.EXPORT_EMPTY")}</div>`;
|
|
3314
|
+
} else if (exportMode === "all") {
|
|
3315
|
+
body = `<div class="export-all-desc">${t("CONFIG.EXPORT_ALL_DESC")}</div>`;
|
|
3316
|
+
} else if (exportMode === "manual") {
|
|
3317
|
+
body = `<div class="export-list">${exportTests.map((test) => `
|
|
3318
|
+
<label class="export-row">
|
|
3319
|
+
<input type="checkbox" data-export-test="${test.id}" ${exportSelectedIds.has(test.id) ? "checked" : ""} />
|
|
3320
|
+
<span class="export-row-name">${escHtml(test.name)}</span>
|
|
3321
|
+
${(test.tags ?? []).map((tag) => `<span class="export-row-tag">${escHtml(tag)}</span>`).join("")}
|
|
3322
|
+
</label>`).join("")}</div>`;
|
|
3323
|
+
} else {
|
|
3324
|
+
const allTags = [...new Set(exportTests.flatMap((test) => test.tags ?? []))].sort();
|
|
3325
|
+
if (!allTags.length) {
|
|
3326
|
+
body = `<div class="export-empty">${t("CONFIG.EXPORT_NO_TAGS")}</div>`;
|
|
3327
|
+
} else {
|
|
3328
|
+
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>`;
|
|
3329
|
+
const matched = selectTestsForExport(exportTests, "tags", { tags: exportSelectedTags });
|
|
3330
|
+
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>
|
|
3331
|
+
<div class="export-list">${matched.map((test) => `
|
|
3332
|
+
<div class="export-row export-row-static">
|
|
3333
|
+
<span class="export-row-name">${escHtml(test.name)}</span>
|
|
3334
|
+
${(test.tags ?? []).map((tag) => `<span class="export-row-tag">${escHtml(tag)}</span>`).join("")}
|
|
3335
|
+
</div>`).join("")}</div>`;
|
|
3336
|
+
body = chips + preview;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return `
|
|
3340
|
+
<div class="export-overlay" id="export-overlay">
|
|
3341
|
+
<div class="export-modal">
|
|
3342
|
+
<div class="export-hd">${t("CONFIG.EXPORT_DIALOG_TITLE")}</div>
|
|
3343
|
+
${exportTests.length ? `<div class="export-modes">
|
|
3344
|
+
${modeBtn("all", "CONFIG.EXPORT_MODE_ALL")}
|
|
3345
|
+
${modeBtn("manual", "CONFIG.EXPORT_MODE_MANUAL")}
|
|
3346
|
+
${modeBtn("tags", "CONFIG.EXPORT_MODE_TAGS")}
|
|
3347
|
+
</div>` : ""}
|
|
3348
|
+
<div class="export-body">${body}</div>
|
|
3349
|
+
<div class="export-ft">
|
|
3350
|
+
<span class="export-count">${t("CONFIG.EXPORT_COUNT")} <b>${count}</b></span>
|
|
3351
|
+
<span class="export-ft-actions">
|
|
3352
|
+
<button id="btn-export-confirm" class="btn-export-confirm" ${count === 0 ? "disabled" : ""}>${t("CONFIG.EXPORT_CONFIRM")}</button>
|
|
3353
|
+
<button id="btn-export-cancel">${t("CONFIG.EXPORT_CANCEL")}</button>
|
|
3354
|
+
</span>
|
|
3355
|
+
</div>
|
|
3356
|
+
</div>
|
|
3357
|
+
</div>`;
|
|
3358
|
+
}
|
|
2908
3359
|
function renderConfiguration(state, t) {
|
|
2909
|
-
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden } = state;
|
|
3360
|
+
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes } = state;
|
|
2910
3361
|
const langOptions = LANGS.map(
|
|
2911
3362
|
(l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
|
|
2912
3363
|
).join("");
|
|
@@ -2958,6 +3409,26 @@ function renderConfiguration(state, t) {
|
|
|
2958
3409
|
</label>
|
|
2959
3410
|
</div>
|
|
2960
3411
|
|
|
3412
|
+
<!-- Widget position (draggable widget) -->
|
|
3413
|
+
<div class="card">
|
|
3414
|
+
<div class="card-hd">${t("CONFIG.WIDGET_POSITION_SECTION")}</div>
|
|
3415
|
+
<div class="check-sub" style="margin-bottom:10px">${t("CONFIG.WIDGET_POSITION_HINT")}</div>
|
|
3416
|
+
<div class="btn-row">
|
|
3417
|
+
<button id="btn-reset-position">${t("CONFIG.WIDGET_POSITION_RESET_BTN")}</button>
|
|
3418
|
+
</div>
|
|
3419
|
+
</div>
|
|
3420
|
+
|
|
3421
|
+
<!-- Recording continuity (cross-app resume TTL) -->
|
|
3422
|
+
<div class="card">
|
|
3423
|
+
<div class="card-hd">${t("CONFIG.RESUME_TTL_SECTION")}</div>
|
|
3424
|
+
<div class="field-row">
|
|
3425
|
+
<span class="field-label">${t("CONFIG.RESUME_TTL_LABEL")}</span>
|
|
3426
|
+
<input type="number" id="resume-ttl-input" min="1" step="1" value="${resumeTtlMinutes}"
|
|
3427
|
+
style="width:80px;padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;color:#c9d1d9;font-size:12px;outline:none" />
|
|
3428
|
+
</div>
|
|
3429
|
+
<div class="check-sub" style="margin-top:8px">${t("CONFIG.RESUME_TTL_HINT")}</div>
|
|
3430
|
+
</div>
|
|
3431
|
+
|
|
2961
3432
|
<!-- Selector Strategy -->
|
|
2962
3433
|
<div class="card card-wide">
|
|
2963
3434
|
<div class="card-hd">${t("CONFIG.SELECTOR_SECTION")}</div>
|
|
@@ -2977,13 +3448,13 @@ function renderConfiguration(state, t) {
|
|
|
2977
3448
|
<div class="card card-wide">
|
|
2978
3449
|
<div class="card-hd">${t("CONFIG.FOLDER_SECTION")}</div>
|
|
2979
3450
|
<div class="fs-layout">
|
|
2980
|
-
<pre class="fs-tree">cypress/ <span style="color:#484f58"
|
|
3451
|
+
<pre class="fs-tree">cypress/ <span style="color:#484f58">${t("RECORDER.FS_TREE_PICK_HINT")}</span>
|
|
2981
3452
|
\u2514\u2500\u2500 e2e/
|
|
2982
3453
|
\u2514\u2500\u2500 *.cy.ts</pre>
|
|
2983
3454
|
<div class="fs-right">
|
|
2984
3455
|
<div class="fs-status">
|
|
2985
3456
|
<span class="fs-dot ${filesystemGranted ? "on" : "off"}"></span>
|
|
2986
|
-
${filesystemGranted && cypressFolderName ? `<span>${t("CONFIG.FOLDER_CONNECTED")}</span> <span class="fs-folder">\u{1F4C1} ${cypressFolderName}</span>` : `<span>${t("CONFIG.FOLDER_NOT_SET")}</span>`}
|
|
3457
|
+
${filesystemGranted && cypressFolderName ? `<span>${t("CONFIG.FOLDER_CONNECTED")}</span> <span class="fs-folder">\u{1F4C1} ${escHtml(cypressFolderName)}</span>` : `<span>${t("CONFIG.FOLDER_NOT_SET")}</span>`}
|
|
2987
3458
|
</div>
|
|
2988
3459
|
<div class="btn-row">
|
|
2989
3460
|
<button id="btn-change-folder">
|
|
@@ -3008,7 +3479,7 @@ function renderConfiguration(state, t) {
|
|
|
3008
3479
|
</div>
|
|
3009
3480
|
</div>
|
|
3010
3481
|
|
|
3011
|
-
</div
|
|
3482
|
+
</div>${renderExportOverlay(state, t)}`;
|
|
3012
3483
|
}
|
|
3013
3484
|
|
|
3014
3485
|
// src/components/configuration/configuration.ts
|
|
@@ -3021,6 +3492,12 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3021
3492
|
selectorStrategy = "data-cy";
|
|
3022
3493
|
smartSelectorEnabled = true;
|
|
3023
3494
|
startHidden = false;
|
|
3495
|
+
resumeTtlMinutes = DEFAULT_RESUME_TTL_MINUTES;
|
|
3496
|
+
isExporting = false;
|
|
3497
|
+
exportMode = "all";
|
|
3498
|
+
exportTests = [];
|
|
3499
|
+
exportSelectedIds = /* @__PURE__ */ new Set();
|
|
3500
|
+
exportSelectedTags = /* @__PURE__ */ new Set();
|
|
3024
3501
|
filesystemGranted = false;
|
|
3025
3502
|
cypressFolderName = null;
|
|
3026
3503
|
constructor() {
|
|
@@ -3047,6 +3524,11 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3047
3524
|
this.selectorStrategy = config?.["selectorStrategy"] ?? "data-cy";
|
|
3048
3525
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
3049
3526
|
this.startHidden = config?.["startHidden"] === "true";
|
|
3527
|
+
const ttlRaw = config?.[RESUME_TTL_CONFIG_KEY];
|
|
3528
|
+
if (ttlRaw !== void 0 && ttlRaw !== null) {
|
|
3529
|
+
const ttl = Number(ttlRaw);
|
|
3530
|
+
this.resumeTtlMinutes = Number.isFinite(ttl) && ttl > 0 ? ttl : DEFAULT_RESUME_TTL_MINUTES;
|
|
3531
|
+
}
|
|
3050
3532
|
this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
|
|
3051
3533
|
const handle = config?.["cypressDirectoryHandle"];
|
|
3052
3534
|
this.cypressFolderName = handle?.name ?? null;
|
|
@@ -3070,6 +3552,16 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3070
3552
|
this.dispatchEvent(new CustomEvent("starthiddenchange", { detail: checked, bubbles: true, composed: true }));
|
|
3071
3553
|
this.render();
|
|
3072
3554
|
}
|
|
3555
|
+
async onResumeTtlChange(minutes) {
|
|
3556
|
+
const safe = Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes) : DEFAULT_RESUME_TTL_MINUTES;
|
|
3557
|
+
this.resumeTtlMinutes = safe;
|
|
3558
|
+
await this.persistence.setConfig({ [RESUME_TTL_CONFIG_KEY]: safe });
|
|
3559
|
+
this.render();
|
|
3560
|
+
}
|
|
3561
|
+
onResetWidgetPosition() {
|
|
3562
|
+
this.dispatchEvent(new CustomEvent("resetwidgetposition", { bubbles: true, composed: true }));
|
|
3563
|
+
showToast(this.t("CONFIG.WIDGET_POSITION_RESET_DONE"));
|
|
3564
|
+
}
|
|
3073
3565
|
async onSmartSelectorChange(enabled) {
|
|
3074
3566
|
this.smartSelectorEnabled = enabled;
|
|
3075
3567
|
await this.persistence.setConfig({ smartSelectorEnabled: enabled ? "true" : "false" });
|
|
@@ -3099,8 +3591,7 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3099
3591
|
this.cypressFolderName = null;
|
|
3100
3592
|
this.render();
|
|
3101
3593
|
}
|
|
3102
|
-
|
|
3103
|
-
const tests = await this.persistence.getAllTests();
|
|
3594
|
+
downloadTests(tests) {
|
|
3104
3595
|
const blob = new Blob([JSON.stringify({ tests, interceptors: [] }, null, 2)], { type: "application/json" });
|
|
3105
3596
|
const url = URL.createObjectURL(blob);
|
|
3106
3597
|
const a = document.createElement("a");
|
|
@@ -3109,6 +3600,53 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3109
3600
|
a.click();
|
|
3110
3601
|
URL.revokeObjectURL(url);
|
|
3111
3602
|
}
|
|
3603
|
+
/** Downloads every saved test (used by the "Todo" mode and as a direct API). */
|
|
3604
|
+
async exportAllData() {
|
|
3605
|
+
const tests = await this.persistence.getAllTests();
|
|
3606
|
+
this.downloadTests(tests);
|
|
3607
|
+
}
|
|
3608
|
+
/** Opens the export selection dialog, loading the current tests. */
|
|
3609
|
+
async openExportDialog() {
|
|
3610
|
+
this.exportTests = await this.persistence.getAllTests();
|
|
3611
|
+
this.exportMode = "all";
|
|
3612
|
+
this.exportSelectedIds = /* @__PURE__ */ new Set();
|
|
3613
|
+
this.exportSelectedTags = /* @__PURE__ */ new Set();
|
|
3614
|
+
this.isExporting = true;
|
|
3615
|
+
this.render();
|
|
3616
|
+
}
|
|
3617
|
+
cancelExport() {
|
|
3618
|
+
this.isExporting = false;
|
|
3619
|
+
this.exportSelectedIds.clear();
|
|
3620
|
+
this.exportSelectedTags.clear();
|
|
3621
|
+
this.render();
|
|
3622
|
+
}
|
|
3623
|
+
setExportMode(mode) {
|
|
3624
|
+
this.exportMode = mode;
|
|
3625
|
+
this.render();
|
|
3626
|
+
}
|
|
3627
|
+
toggleExportTest(id) {
|
|
3628
|
+
if (this.exportSelectedIds.has(id)) this.exportSelectedIds.delete(id);
|
|
3629
|
+
else this.exportSelectedIds.add(id);
|
|
3630
|
+
this.render();
|
|
3631
|
+
}
|
|
3632
|
+
toggleExportTag(tag) {
|
|
3633
|
+
if (this.exportSelectedTags.has(tag)) this.exportSelectedTags.delete(tag);
|
|
3634
|
+
else this.exportSelectedTags.add(tag);
|
|
3635
|
+
this.render();
|
|
3636
|
+
}
|
|
3637
|
+
/** Downloads the tests resolved by the current mode + selection. No-op if empty. */
|
|
3638
|
+
confirmExport() {
|
|
3639
|
+
const subset = selectTestsForExport(this.exportTests, this.exportMode, {
|
|
3640
|
+
ids: this.exportSelectedIds,
|
|
3641
|
+
tags: this.exportSelectedTags
|
|
3642
|
+
});
|
|
3643
|
+
if (!subset.length) return;
|
|
3644
|
+
this.downloadTests(subset);
|
|
3645
|
+
this.isExporting = false;
|
|
3646
|
+
this.exportSelectedIds.clear();
|
|
3647
|
+
this.exportSelectedTags.clear();
|
|
3648
|
+
this.render();
|
|
3649
|
+
}
|
|
3112
3650
|
async importAllData(file) {
|
|
3113
3651
|
const text = await file.text();
|
|
3114
3652
|
let data;
|
|
@@ -3120,7 +3658,6 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3120
3658
|
if (!data || !Array.isArray(data.tests) || !Array.isArray(data.interceptors)) {
|
|
3121
3659
|
throw new Error(this.t("CONFIG.JSON_BAD_FORMAT"));
|
|
3122
3660
|
}
|
|
3123
|
-
await this.persistence.clearAllData();
|
|
3124
3661
|
await this.persistence.ingestFileData(data.tests, data.interceptors);
|
|
3125
3662
|
}
|
|
3126
3663
|
render() {
|
|
@@ -3131,7 +3668,13 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3131
3668
|
filesystemGranted: this.filesystemGranted,
|
|
3132
3669
|
cypressFolderName: this.cypressFolderName,
|
|
3133
3670
|
smartSelectorEnabled: this.smartSelectorEnabled,
|
|
3134
|
-
startHidden: this.startHidden
|
|
3671
|
+
startHidden: this.startHidden,
|
|
3672
|
+
resumeTtlMinutes: this.resumeTtlMinutes,
|
|
3673
|
+
isExporting: this.isExporting,
|
|
3674
|
+
exportMode: this.exportMode,
|
|
3675
|
+
exportTests: this.exportTests,
|
|
3676
|
+
exportSelectedIds: this.exportSelectedIds,
|
|
3677
|
+
exportSelectedTags: this.exportSelectedTags
|
|
3135
3678
|
}, this.t.bind(this))}`;
|
|
3136
3679
|
;
|
|
3137
3680
|
this.shadow.getElementById("lang-select").addEventListener(
|
|
@@ -3150,13 +3693,29 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
3150
3693
|
"change",
|
|
3151
3694
|
(e) => this.onStartHiddenChange(e.target.checked)
|
|
3152
3695
|
);
|
|
3696
|
+
this.shadow.getElementById("resume-ttl-input").addEventListener(
|
|
3697
|
+
"change",
|
|
3698
|
+
(e) => this.onResumeTtlChange(Number(e.target.value))
|
|
3699
|
+
);
|
|
3700
|
+
this.shadow.getElementById("btn-reset-position")?.addEventListener("click", () => this.onResetWidgetPosition());
|
|
3153
3701
|
this.shadow.getElementById("selector-strategy").addEventListener(
|
|
3154
3702
|
"change",
|
|
3155
3703
|
(e) => this.onSelectorStrategyChange(e.target.value)
|
|
3156
3704
|
);
|
|
3157
3705
|
this.shadow.getElementById("btn-change-folder")?.addEventListener("click", () => this.changeFolder());
|
|
3158
3706
|
this.shadow.getElementById("btn-revoke")?.addEventListener("click", () => this.revokeAccess());
|
|
3159
|
-
this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.
|
|
3707
|
+
this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.openExportDialog());
|
|
3708
|
+
this.shadow.getElementById("btn-export-confirm")?.addEventListener("click", () => this.confirmExport());
|
|
3709
|
+
this.shadow.getElementById("btn-export-cancel")?.addEventListener("click", () => this.cancelExport());
|
|
3710
|
+
this.shadow.querySelectorAll("[data-export-mode]").forEach(
|
|
3711
|
+
(el) => el.addEventListener("click", () => this.setExportMode(el.dataset["exportMode"]))
|
|
3712
|
+
);
|
|
3713
|
+
this.shadow.querySelectorAll("[data-export-test]").forEach(
|
|
3714
|
+
(el) => el.addEventListener("change", () => this.toggleExportTest(Number(el.dataset["exportTest"])))
|
|
3715
|
+
);
|
|
3716
|
+
this.shadow.querySelectorAll("[data-export-tag]").forEach(
|
|
3717
|
+
(el) => el.addEventListener("click", () => this.toggleExportTag(el.dataset["exportTag"] ?? ""))
|
|
3718
|
+
);
|
|
3160
3719
|
this.shadow.getElementById("file-input").addEventListener("change", async (e) => {
|
|
3161
3720
|
const file = e.target.files?.[0];
|
|
3162
3721
|
if (!file) return;
|
|
@@ -3266,18 +3825,21 @@ var ADVANCED_TEST_EDITOR_STYLES = `
|
|
|
3266
3825
|
}
|
|
3267
3826
|
.btn-copy:hover { background: #30363d; color: #e6edf3; }
|
|
3268
3827
|
.sidebar-toolbar {
|
|
3269
|
-
display: flex; gap: 4px; padding: 6px 6px 4px;
|
|
3828
|
+
display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 6px 4px;
|
|
3270
3829
|
border-bottom: 1px solid #21262d; flex-shrink: 0; background: #0d1117;
|
|
3271
3830
|
}
|
|
3272
3831
|
.btn-toolbar {
|
|
3273
|
-
flex: 1; padding: 4px 6px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
|
|
3832
|
+
flex: 1 1 calc(50% - 2px); padding: 4px 6px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
|
|
3274
3833
|
font-size: 10px; font-weight: 500; background: #161b22; color: #8b949e;
|
|
3275
3834
|
transition: background 0.12s, color 0.12s; white-space: nowrap; overflow: hidden;
|
|
3276
3835
|
text-overflow: ellipsis;
|
|
3277
3836
|
}
|
|
3837
|
+
.btn-toolbar.btn-full { flex: 1 1 100%; }
|
|
3278
3838
|
.btn-toolbar:hover { background: #30363d; color: #e6edf3; }
|
|
3279
3839
|
.btn-toolbar.btn-new { color: #3fb950; border-color: #238636; }
|
|
3280
3840
|
.btn-toolbar.btn-new:hover { background: #238636; color: #fff; }
|
|
3841
|
+
.btn-toolbar.btn-new-folder { color: #d29922; border-color: #9e6a03; }
|
|
3842
|
+
.btn-toolbar.btn-new-folder:hover { background: #9e6a03; color: #fff; }
|
|
3281
3843
|
.new-file-form {
|
|
3282
3844
|
padding: 6px; border-bottom: 1px solid #21262d; display: flex; flex-direction: column; gap: 4px;
|
|
3283
3845
|
}
|
|
@@ -3310,7 +3872,7 @@ function renderNoPermission(needsReauth, t) {
|
|
|
3310
3872
|
</div>`;
|
|
3311
3873
|
}
|
|
3312
3874
|
function renderAdvancedEditor(state, t) {
|
|
3313
|
-
const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, testNotes, saveButtonEnabled, isCreatingFile, collapsedDirs, sidebarWidth } = state;
|
|
3875
|
+
const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, testNotes, saveButtonEnabled, isCreatingFile, isCreatingFolder, collapsedDirs, sidebarWidth } = state;
|
|
3314
3876
|
const treeHtml = e2eTree.length ? renderTree(e2eTree, selectedFile, collapsedDirs) : `<div class="tree-item" style="color:#6c7a99">${t("ADVANCED_EDITOR.NO_FILES")}</div>`;
|
|
3315
3877
|
const contentHtml = selectedFileContent ? `<div class="file-name">\u{1F4C4} ${escHtml(selectedFile?.name ?? "")}</div>
|
|
3316
3878
|
<pre class="pre-file">${syntaxHighlight(selectedFileContent.slice(0, 6e3))}${selectedFileContent.length > 6e3 ? "\n..." : ""}</pre>` : `<div class="placeholder">${t("ADVANCED_EDITOR.SELECT_FILE")}</div>`;
|
|
@@ -3337,16 +3899,25 @@ function renderAdvancedEditor(state, t) {
|
|
|
3337
3899
|
<button id="btn-new-file-cancel" class="btn-cancel-form">${t("ADVANCED_EDITOR.NEW_FILE_CANCEL")}</button>
|
|
3338
3900
|
</div>
|
|
3339
3901
|
</div>` : "";
|
|
3902
|
+
const newFolderForm = isCreatingFolder ? `<div class="new-file-form">
|
|
3903
|
+
<input id="input-new-folder" type="text" placeholder="${escHtml(t("ADVANCED_EDITOR.NEW_FOLDER_PLACEHOLDER"))}" autocomplete="off" />
|
|
3904
|
+
<div class="new-file-actions">
|
|
3905
|
+
<button id="btn-new-folder-confirm" class="btn-confirm">${t("ADVANCED_EDITOR.NEW_FILE_CONFIRM")}</button>
|
|
3906
|
+
<button id="btn-new-folder-cancel" class="btn-cancel-form">${t("ADVANCED_EDITOR.NEW_FILE_CANCEL")}</button>
|
|
3907
|
+
</div>
|
|
3908
|
+
</div>` : "";
|
|
3340
3909
|
const toolbar = `
|
|
3341
3910
|
<div class="sidebar-toolbar">
|
|
3342
3911
|
<button id="btn-new-file" class="btn-toolbar btn-new">${t("ADVANCED_EDITOR.NEW_FILE_BTN")}</button>
|
|
3343
|
-
<button id="btn-
|
|
3912
|
+
<button id="btn-new-folder" class="btn-toolbar btn-new-folder">${t("ADVANCED_EDITOR.NEW_FOLDER_BTN")}</button>
|
|
3913
|
+
<button id="btn-refresh" class="btn-toolbar btn-full">${t("ADVANCED_EDITOR.REFRESH_BTN")}</button>
|
|
3344
3914
|
</div>`;
|
|
3345
3915
|
return `
|
|
3346
3916
|
<div class="layout">
|
|
3347
3917
|
<div class="sidebar" style="width:${sidebarWidth}px">
|
|
3348
3918
|
${toolbar}
|
|
3349
3919
|
${newFileForm}
|
|
3920
|
+
${newFolderForm}
|
|
3350
3921
|
<div class="tree-scroll">${treeHtml}</div>
|
|
3351
3922
|
</div>
|
|
3352
3923
|
<div id="resize-handle" class="resize-handle"></div>
|
|
@@ -3417,6 +3988,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3417
3988
|
previewFileName = null;
|
|
3418
3989
|
previewFileContent = null;
|
|
3419
3990
|
isCreatingFile = false;
|
|
3991
|
+
isCreatingFolder = false;
|
|
3420
3992
|
collapsedDirs = /* @__PURE__ */ new Set();
|
|
3421
3993
|
sidebarWidth = 220;
|
|
3422
3994
|
hasPermission = false;
|
|
@@ -3479,7 +4051,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3479
4051
|
const name = rawName.trim().replace(/\.cy\.ts$/, "");
|
|
3480
4052
|
if (!name) return;
|
|
3481
4053
|
const fileName = `${name}.cy.ts`;
|
|
3482
|
-
const template = `describe('${name}', () => {
|
|
4054
|
+
const template = `describe('${escapeSingleQuotes(name)}', () => {
|
|
3483
4055
|
|
|
3484
4056
|
it('should ', () => {
|
|
3485
4057
|
|
|
@@ -3497,6 +4069,17 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3497
4069
|
this.isCreatingFile = false;
|
|
3498
4070
|
await this.getFoldersData();
|
|
3499
4071
|
}
|
|
4072
|
+
async createNewFolder(rawName) {
|
|
4073
|
+
if (!this._e2eHandle) return;
|
|
4074
|
+
const name = rawName.trim().replace(/[/\\]/g, "");
|
|
4075
|
+
if (!name) return;
|
|
4076
|
+
try {
|
|
4077
|
+
await this._e2eHandle.getDirectoryHandle(name, { create: true });
|
|
4078
|
+
} catch {
|
|
4079
|
+
}
|
|
4080
|
+
this.isCreatingFolder = false;
|
|
4081
|
+
await this.getFoldersData();
|
|
4082
|
+
}
|
|
3500
4083
|
async refreshTree() {
|
|
3501
4084
|
await this.getFoldersData();
|
|
3502
4085
|
}
|
|
@@ -3554,7 +4137,8 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3554
4137
|
fileName: this.selectedFile?.name ?? "",
|
|
3555
4138
|
testId: this.testId,
|
|
3556
4139
|
itBlock: this.testItBlock,
|
|
3557
|
-
interceptorsBlock: this.interceptorsBlock
|
|
4140
|
+
interceptorsBlock: this.interceptorsBlock,
|
|
4141
|
+
notes: this.testNotes
|
|
3558
4142
|
},
|
|
3559
4143
|
bubbles: true,
|
|
3560
4144
|
composed: true
|
|
@@ -3631,6 +4215,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3631
4215
|
testNotes: this.testNotes,
|
|
3632
4216
|
saveButtonEnabled: this.saveButtonEnabled,
|
|
3633
4217
|
isCreatingFile: this.isCreatingFile,
|
|
4218
|
+
isCreatingFolder: this.isCreatingFolder,
|
|
3634
4219
|
collapsedDirs: this.collapsedDirs,
|
|
3635
4220
|
sidebarWidth: this.sidebarWidth
|
|
3636
4221
|
}, this.t.bind(this))}`;
|
|
@@ -3654,9 +4239,16 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3654
4239
|
this.shadow.getElementById("btn-close")?.addEventListener("click", () => this.closePreview());
|
|
3655
4240
|
this.shadow.getElementById("btn-new-file")?.addEventListener("click", () => {
|
|
3656
4241
|
this.isCreatingFile = !this.isCreatingFile;
|
|
4242
|
+
this.isCreatingFolder = false;
|
|
3657
4243
|
this.render();
|
|
3658
4244
|
if (this.isCreatingFile) this.shadow.getElementById("input-new-file")?.focus();
|
|
3659
4245
|
});
|
|
4246
|
+
this.shadow.getElementById("btn-new-folder")?.addEventListener("click", () => {
|
|
4247
|
+
this.isCreatingFolder = !this.isCreatingFolder;
|
|
4248
|
+
this.isCreatingFile = false;
|
|
4249
|
+
this.render();
|
|
4250
|
+
if (this.isCreatingFolder) this.shadow.getElementById("input-new-folder")?.focus();
|
|
4251
|
+
});
|
|
3660
4252
|
this.shadow.getElementById("btn-refresh")?.addEventListener("click", () => this.refreshTree());
|
|
3661
4253
|
this.shadow.getElementById("btn-new-file-confirm")?.addEventListener("click", () => {
|
|
3662
4254
|
const input2 = this.shadow.getElementById("input-new-file");
|
|
@@ -3675,6 +4267,23 @@ var AdvancedTestEditorElement = class extends HTMLElement {
|
|
|
3675
4267
|
this.render();
|
|
3676
4268
|
}
|
|
3677
4269
|
});
|
|
4270
|
+
this.shadow.getElementById("btn-new-folder-confirm")?.addEventListener("click", () => {
|
|
4271
|
+
const folderInput2 = this.shadow.getElementById("input-new-folder");
|
|
4272
|
+
this.createNewFolder(folderInput2?.value ?? "");
|
|
4273
|
+
});
|
|
4274
|
+
this.shadow.getElementById("btn-new-folder-cancel")?.addEventListener("click", () => {
|
|
4275
|
+
this.isCreatingFolder = false;
|
|
4276
|
+
this.render();
|
|
4277
|
+
});
|
|
4278
|
+
const folderInput = this.shadow.getElementById("input-new-folder");
|
|
4279
|
+
folderInput?.addEventListener("keydown", (e) => {
|
|
4280
|
+
if (e.key === "Enter") {
|
|
4281
|
+
this.createNewFolder(folderInput.value);
|
|
4282
|
+
} else if (e.key === "Escape") {
|
|
4283
|
+
this.isCreatingFolder = false;
|
|
4284
|
+
this.render();
|
|
4285
|
+
}
|
|
4286
|
+
});
|
|
3678
4287
|
}
|
|
3679
4288
|
};
|
|
3680
4289
|
if (!customElements.get("advanced-test-editor")) {
|
|
@@ -3760,7 +4369,30 @@ var FILE_PREVIEW_STYLES = `
|
|
|
3760
4369
|
.btn-save:hover { background: #2ea043; border-color: #2ea043; }
|
|
3761
4370
|
.btn-launch { background: transparent; border-color: #e3b341; color: #e3b341; }
|
|
3762
4371
|
.btn-launch:hover { background: rgba(227,179,65,0.1); }
|
|
4372
|
+
.btn-launch:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
4373
|
+
.btn-launch:disabled:hover { background: transparent; }
|
|
4374
|
+
.launch-hint { font-size: 10px; color: #8b6d3b; align-self: center; }
|
|
4375
|
+
.btn-insert { background: transparent; border-color: #a371f7; color: #a371f7; }
|
|
4376
|
+
.btn-insert:hover { background: rgba(163,113,247,0.12); color: #c8a8ff; }
|
|
3763
4377
|
.btn-diff-active { background: rgba(47,129,247,0.15); border-color: rgba(47,129,247,0.4); color: #2f81f7; }
|
|
4378
|
+
|
|
4379
|
+
/* \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 */
|
|
4380
|
+
.run-result {
|
|
4381
|
+
border-top: 1px solid #21262d; padding: 8px 12px; background: #0d1117;
|
|
4382
|
+
max-height: 220px; overflow-y: auto; flex-shrink: 0;
|
|
4383
|
+
scrollbar-width: thin; scrollbar-color: #30363d transparent;
|
|
4384
|
+
}
|
|
4385
|
+
.run-status { font-size: 12px; font-weight: 600; }
|
|
4386
|
+
.run-passed .run-status { color: #3fb950; }
|
|
4387
|
+
.run-failed .run-status { color: #f85149; }
|
|
4388
|
+
.run-running .run-status { color: #e3b341; }
|
|
4389
|
+
.run-error .run-status { color: #f85149; }
|
|
4390
|
+
.run-output {
|
|
4391
|
+
margin: 6px 0 0; padding: 8px 10px; background: #161b22; border: 1px solid #21262d;
|
|
4392
|
+
border-radius: 5px; font-size: 10px; color: #c9d1d9; line-height: 1.5;
|
|
4393
|
+
font-family: 'Cascadia Code','Fira Code','Consolas',monospace;
|
|
4394
|
+
white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow-y: auto;
|
|
4395
|
+
}
|
|
3764
4396
|
`;
|
|
3765
4397
|
|
|
3766
4398
|
// src/components/file-preview/file-preview.template.ts
|
|
@@ -3817,8 +4449,14 @@ function buildDiffHtml(original, current, t) {
|
|
|
3817
4449
|
return `<div class="diff-line ${type}"><span class="diff-sign">${sign}</span><span>${escHtml(line)}</span></div>`;
|
|
3818
4450
|
}).join("");
|
|
3819
4451
|
}
|
|
4452
|
+
var RUN_STATUS_KEY = {
|
|
4453
|
+
running: "FILE_PREVIEW.LAUNCH_RUNNING",
|
|
4454
|
+
passed: "FILE_PREVIEW.LAUNCH_PASSED",
|
|
4455
|
+
failed: "FILE_PREVIEW.LAUNCH_FAILED",
|
|
4456
|
+
error: "FILE_PREVIEW.LAUNCH_NO_RUNNER"
|
|
4457
|
+
};
|
|
3820
4458
|
function renderFilePreview(state, t) {
|
|
3821
|
-
const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel } = state;
|
|
4459
|
+
const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel, isLocal, runState, runOutput } = state;
|
|
3822
4460
|
const blocksPanelHtml = itBlock || interceptorsBlock ? `
|
|
3823
4461
|
<div class="blocks-panel">
|
|
3824
4462
|
${itBlock ? `
|
|
@@ -3839,6 +4477,13 @@ function renderFilePreview(state, t) {
|
|
|
3839
4477
|
</div>` : ""}
|
|
3840
4478
|
</div>` : "";
|
|
3841
4479
|
const hasChanges = originalContent !== null && originalContent !== (fileContent ?? "");
|
|
4480
|
+
const hasBlocks = !!(itBlock || interceptorsBlock);
|
|
4481
|
+
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>
|
|
4482
|
+
<span class="launch-hint">${t("FILE_PREVIEW.LAUNCH_LOCAL_ONLY")}</span>`;
|
|
4483
|
+
const runResultHtml = runState !== "idle" ? `<div class="run-result run-${runState}">
|
|
4484
|
+
<span class="run-status">${t(RUN_STATUS_KEY[runState])}</span>
|
|
4485
|
+
${runOutput ? `<pre class="run-output">${escHtml(runOutput.slice(0, 8e3))}</pre>` : ""}
|
|
4486
|
+
</div>` : "";
|
|
3842
4487
|
return `
|
|
3843
4488
|
<div class="container">
|
|
3844
4489
|
<div class="header">
|
|
@@ -3849,9 +4494,11 @@ function renderFilePreview(state, t) {
|
|
|
3849
4494
|
${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea>`}
|
|
3850
4495
|
${blocksPanelHtml}
|
|
3851
4496
|
</div>
|
|
4497
|
+
${runResultHtml}
|
|
3852
4498
|
<div class="footer">
|
|
3853
|
-
|
|
4499
|
+
${launchBtnHtml}
|
|
3854
4500
|
${hasChanges ? `<button id="btn-diff" class="${showDiff ? "btn-diff-active" : ""}">${t("FILE_PREVIEW.DIFF_BTN")}</button>` : ""}
|
|
4501
|
+
${hasBlocks && !showDiff ? `<button id="btn-insert" class="btn-insert" title="${t("FILE_PREVIEW.INSERT_TITLE")}">${t("FILE_PREVIEW.INSERT_BTN")}</button>` : ""}
|
|
3855
4502
|
<button id="btn-save" class="btn-save">${t("FILE_PREVIEW.SAVE_BTN")}</button>
|
|
3856
4503
|
<button id="btn-close">${escHtml(closeLabel || t("FILE_PREVIEW.CLOSE_BTN"))}</button>
|
|
3857
4504
|
</div>
|
|
@@ -3867,11 +4514,18 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
3867
4514
|
translation = translationService;
|
|
3868
4515
|
itBlock = "";
|
|
3869
4516
|
interceptorsBlock = "";
|
|
4517
|
+
notes = "";
|
|
3870
4518
|
commands = [];
|
|
3871
4519
|
interceptors = [];
|
|
4520
|
+
/** Endpoint of the local Cypress runner. Configurable; defaults to the bundled runner's port. */
|
|
4521
|
+
runnerUrl = "http://localhost:8123/run-test";
|
|
4522
|
+
/** Whether the app is served locally — gates the launch button. Overridable for tests. */
|
|
4523
|
+
isLocal = isLocalHost(window.location.hostname);
|
|
3872
4524
|
_fileContent = null;
|
|
3873
4525
|
_originalContent = null;
|
|
3874
4526
|
_showDiff = false;
|
|
4527
|
+
_runState = "idle";
|
|
4528
|
+
_runOutput = "";
|
|
3875
4529
|
constructor() {
|
|
3876
4530
|
super();
|
|
3877
4531
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -3899,15 +4553,34 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
3899
4553
|
onClose() {
|
|
3900
4554
|
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
3901
4555
|
}
|
|
4556
|
+
/**
|
|
4557
|
+
* Runs the current spec headless via the local runner and shows the result.
|
|
4558
|
+
* No-op off localhost. Never throws — connection failures surface as an
|
|
4559
|
+
* "error" state + toast instead of an unhandled rejection.
|
|
4560
|
+
*/
|
|
3902
4561
|
async launchTest(specPath) {
|
|
3903
|
-
|
|
4562
|
+
if (!this.isLocal) return;
|
|
4563
|
+
const path = specPath ?? this.fileName ?? "";
|
|
3904
4564
|
if (!path) return;
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
4565
|
+
this._runState = "running";
|
|
4566
|
+
this._runOutput = "";
|
|
4567
|
+
this.render();
|
|
4568
|
+
try {
|
|
4569
|
+
const response = await fetch(this.runnerUrl, {
|
|
4570
|
+
method: "POST",
|
|
4571
|
+
headers: { "Content-Type": "application/json" },
|
|
4572
|
+
body: JSON.stringify({ specPath: path })
|
|
4573
|
+
});
|
|
4574
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
4575
|
+
const result = await response.json();
|
|
4576
|
+
this._runOutput = result.output ?? "";
|
|
4577
|
+
this._runState = result.success ? "passed" : "failed";
|
|
4578
|
+
} catch {
|
|
4579
|
+
this._runState = "error";
|
|
4580
|
+
this._runOutput = "";
|
|
4581
|
+
showToast(this.t("FILE_PREVIEW.LAUNCH_NO_RUNNER"), false);
|
|
4582
|
+
}
|
|
4583
|
+
this.render();
|
|
3911
4584
|
}
|
|
3912
4585
|
copyToClipboard(text) {
|
|
3913
4586
|
navigator.clipboard?.writeText(text);
|
|
@@ -3916,6 +4589,32 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
3916
4589
|
this._showDiff = !this._showDiff;
|
|
3917
4590
|
this.render();
|
|
3918
4591
|
}
|
|
4592
|
+
/**
|
|
4593
|
+
* Merges the it() and beforeEach() blocks into the current editor content
|
|
4594
|
+
* using the same logic as the automatic "Insert into file" action, so the
|
|
4595
|
+
* user does not have to copy/paste manually. Leaves the content untouched
|
|
4596
|
+
* (and warns) when the file has no valid describe() block to insert into.
|
|
4597
|
+
*/
|
|
4598
|
+
insertBlocks() {
|
|
4599
|
+
const base = this.textarea?.value ?? this._fileContent ?? "";
|
|
4600
|
+
let content = base;
|
|
4601
|
+
if (this.interceptorsBlock) {
|
|
4602
|
+
const merged = advancedTestTransformationService.insertBeforeEach(content, this.interceptorsBlock);
|
|
4603
|
+
if (merged) content = merged;
|
|
4604
|
+
}
|
|
4605
|
+
if (this.itBlock) {
|
|
4606
|
+
const comment = this.notes ? advancedTestTransformationService.buildBlockComment(this.notes) + "\n" : "";
|
|
4607
|
+
const merged = advancedTestTransformationService.insertItBlock(content, comment + this.itBlock);
|
|
4608
|
+
if (merged) content = merged;
|
|
4609
|
+
}
|
|
4610
|
+
if (content === base) {
|
|
4611
|
+
showToast(this.t("FILE_PREVIEW.INSERT_ERROR"), false);
|
|
4612
|
+
return;
|
|
4613
|
+
}
|
|
4614
|
+
this._fileContent = content;
|
|
4615
|
+
this.render();
|
|
4616
|
+
showToast(this.t("FILE_PREVIEW.INSERT_DONE"));
|
|
4617
|
+
}
|
|
3919
4618
|
render() {
|
|
3920
4619
|
this.shadow.innerHTML = `<style>${FILE_PREVIEW_STYLES}</style>${renderFilePreview({
|
|
3921
4620
|
fileName: this.fileName,
|
|
@@ -3925,7 +4624,10 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
3925
4624
|
currentContent: this.textarea?.value ?? this._fileContent ?? "",
|
|
3926
4625
|
itBlock: this.itBlock,
|
|
3927
4626
|
interceptorsBlock: this.interceptorsBlock,
|
|
3928
|
-
closeLabel: this.closeLabel
|
|
4627
|
+
closeLabel: this.closeLabel,
|
|
4628
|
+
isLocal: this.isLocal,
|
|
4629
|
+
runState: this._runState,
|
|
4630
|
+
runOutput: this._runOutput
|
|
3929
4631
|
}, this.t.bind(this))}`;
|
|
3930
4632
|
this.textarea = this.shadow.getElementById("editor");
|
|
3931
4633
|
if (this.textarea) {
|
|
@@ -3941,6 +4643,7 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
3941
4643
|
this.copyToClipboard(this.textarea?.value ?? this._fileContent ?? "");
|
|
3942
4644
|
});
|
|
3943
4645
|
this.shadow.getElementById("btn-diff")?.addEventListener("click", () => this.toggleDiff());
|
|
4646
|
+
this.shadow.getElementById("btn-insert")?.addEventListener("click", () => this.insertBlocks());
|
|
3944
4647
|
this.shadow.getElementById("btn-copy-it")?.addEventListener("click", () => {
|
|
3945
4648
|
this.copyToClipboard(this.itBlock);
|
|
3946
4649
|
});
|
|
@@ -3957,15 +4660,37 @@ if (!customElements.get("file-preview")) {
|
|
|
3957
4660
|
import Swal from "sweetalert2";
|
|
3958
4661
|
|
|
3959
4662
|
// src/components/lib-e2e-recorder/lib-e2e-recorder.styles.ts
|
|
4663
|
+
var DIRECTIONS = ["up-left", "up-right", "down-left", "down-right"];
|
|
4664
|
+
function directionBlocks() {
|
|
4665
|
+
return DIRECTIONS.map((dir) => {
|
|
4666
|
+
const sx = dir.endsWith("left") ? -1 : 1;
|
|
4667
|
+
const sy = dir.startsWith("up") ? -1 : 1;
|
|
4668
|
+
const tv = dir.startsWith("up") ? "bottom" : "top";
|
|
4669
|
+
const th = dir.endsWith("left") ? "right" : "left";
|
|
4670
|
+
const ov = tv === "bottom" ? "top" : "bottom";
|
|
4671
|
+
const oh = th === "right" ? "left" : "right";
|
|
4672
|
+
const sel = `.widget[data-expand="${dir}"]`;
|
|
4673
|
+
const labelGeneral = `${sel} .btn-action::after { ${oh}: calc(100% + 9px); ${th}: auto; top: 50%; bottom: auto; transform: translateY(-50%); }`;
|
|
4674
|
+
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%); }`;
|
|
4675
|
+
return `
|
|
4676
|
+
${sel} { --sx: ${sx}; --sy: ${sy}; }
|
|
4677
|
+
${sel} .btn-toggle { ${tv}: 24px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
|
|
4678
|
+
${sel} .btn-pause { ${tv}: 78px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
|
|
4679
|
+
${sel} .btn-action { ${tv}: 28px; ${th}: 28px; ${ov}: auto; ${oh}: auto; }
|
|
4680
|
+
${labelGeneral}
|
|
4681
|
+
${labelEnds}
|
|
4682
|
+
`;
|
|
4683
|
+
}).join("\n");
|
|
4684
|
+
}
|
|
3960
4685
|
function getRecorderStyles(rec, paused) {
|
|
3961
4686
|
return `
|
|
3962
4687
|
:host { all: initial; }
|
|
3963
4688
|
*, *::before, *::after { box-sizing: border-box; }
|
|
3964
4689
|
|
|
3965
4690
|
/*
|
|
3966
|
-
* Invisible 190\xD7190 hit area
|
|
3967
|
-
*
|
|
3968
|
-
*
|
|
4691
|
+
* Invisible 190\xD7190 hit area. Position (left/top) is set from JS so the
|
|
4692
|
+
* widget can be dragged; --sx/--sy + data-expand orient the radial menu so
|
|
4693
|
+
* it always expands toward the viewport interior. Defaults to bottom-right.
|
|
3969
4694
|
*/
|
|
3970
4695
|
.widget {
|
|
3971
4696
|
position: fixed;
|
|
@@ -3974,6 +4699,8 @@ function getRecorderStyles(rec, paused) {
|
|
|
3974
4699
|
width: 190px;
|
|
3975
4700
|
height: 190px;
|
|
3976
4701
|
z-index: 2147483647;
|
|
4702
|
+
--sx: -1;
|
|
4703
|
+
--sy: -1;
|
|
3977
4704
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
3978
4705
|
}
|
|
3979
4706
|
|
|
@@ -3986,7 +4713,8 @@ function getRecorderStyles(rec, paused) {
|
|
|
3986
4713
|
height: 44px;
|
|
3987
4714
|
border-radius: 50%;
|
|
3988
4715
|
border: none;
|
|
3989
|
-
cursor:
|
|
4716
|
+
cursor: grab;
|
|
4717
|
+
touch-action: none;
|
|
3990
4718
|
font-size: 19px;
|
|
3991
4719
|
background: ${rec ? "linear-gradient(135deg,#f85149 0%,#da3633 100%)" : "linear-gradient(135deg,#2f81f7 0%,#1f6feb 100%)"};
|
|
3992
4720
|
color: #fff;
|
|
@@ -3999,7 +4727,7 @@ function getRecorderStyles(rec, paused) {
|
|
|
3999
4727
|
${rec ? "animation: toggle-pulse 2s ease-in-out infinite;" : ""}
|
|
4000
4728
|
}
|
|
4001
4729
|
.btn-toggle:hover { transform: scale(1.1); }
|
|
4002
|
-
.btn-toggle:active { transform: scale(0.93); }
|
|
4730
|
+
.btn-toggle:active { transform: scale(0.93); cursor: grabbing; }
|
|
4003
4731
|
|
|
4004
4732
|
@keyframes toggle-pulse {
|
|
4005
4733
|
0%,100% { box-shadow: 0 4px 20px rgba(248,81,73,.55),0 0 0 4px rgba(248,81,73,.13); }
|
|
@@ -4030,11 +4758,6 @@ function getRecorderStyles(rec, paused) {
|
|
|
4030
4758
|
.btn-pause:active { transform: scale(0.93); }
|
|
4031
4759
|
|
|
4032
4760
|
/* \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 */
|
|
4033
|
-
/*
|
|
4034
|
-
* All three start centered on the toggle button
|
|
4035
|
-
* (toggle center = bottom:46px right:46px from widget edge;
|
|
4036
|
-
* button half = 18px \u2192 bottom:28px right:28px).
|
|
4037
|
-
*/
|
|
4038
4761
|
.btn-action {
|
|
4039
4762
|
position: absolute;
|
|
4040
4763
|
bottom: 28px;
|
|
@@ -4056,12 +4779,11 @@ function getRecorderStyles(rec, paused) {
|
|
|
4056
4779
|
opacity: 0;
|
|
4057
4780
|
transform: scale(0.35);
|
|
4058
4781
|
pointer-events: none;
|
|
4059
|
-
/* Collapse: fast, no spring */
|
|
4060
4782
|
transition: opacity .15s, transform .18s ease-in,
|
|
4061
4783
|
background .15s, color .12s, box-shadow .15s;
|
|
4062
4784
|
}
|
|
4063
4785
|
|
|
4064
|
-
/* Label
|
|
4786
|
+
/* Label chip */
|
|
4065
4787
|
.btn-action::after {
|
|
4066
4788
|
content: attr(data-label);
|
|
4067
4789
|
position: absolute;
|
|
@@ -4081,14 +4803,6 @@ function getRecorderStyles(rec, paused) {
|
|
|
4081
4803
|
border: 1px solid rgba(48,54,61,.8);
|
|
4082
4804
|
box-shadow: 0 2px 8px rgba(0,0,0,.35);
|
|
4083
4805
|
}
|
|
4084
|
-
/* Button 1 (top) \u2014 label above instead of left to avoid overlap with btn 2 */
|
|
4085
|
-
.btn-action[data-n="1"]::after {
|
|
4086
|
-
right: auto;
|
|
4087
|
-
left: 50%;
|
|
4088
|
-
top: auto;
|
|
4089
|
-
bottom: calc(100% + 9px);
|
|
4090
|
-
transform: translateX(-50%);
|
|
4091
|
-
}
|
|
4092
4806
|
.btn-action:hover::after { opacity: 1; }
|
|
4093
4807
|
.btn-action:hover { background: #21262d; color: #e6edf3; }
|
|
4094
4808
|
.btn-action:active { background: #30363d !important; }
|
|
@@ -4101,31 +4815,26 @@ function getRecorderStyles(rec, paused) {
|
|
|
4101
4815
|
background .15s, color .12s, box-shadow .15s;
|
|
4102
4816
|
}
|
|
4103
4817
|
|
|
4104
|
-
/* Arc positions \u2014
|
|
4105
|
-
.widget:hover .btn-action[data-n="1"] { /*
|
|
4106
|
-
transform: translateY(
|
|
4818
|
+
/* Arc positions \u2014 signs come from --sx/--sy (data-expand) */
|
|
4819
|
+
.widget:hover .btn-action[data-n="1"] { /* vertical extreme */
|
|
4820
|
+
transform: translateY(calc(var(--sy) * 90px)) scale(1);
|
|
4107
4821
|
transition-delay: .03s;
|
|
4108
4822
|
}
|
|
4109
|
-
.widget:hover .btn-action[data-n="2"] { /*
|
|
4110
|
-
transform: translate(
|
|
4823
|
+
.widget:hover .btn-action[data-n="2"] { /* diagonal near-vertical */
|
|
4824
|
+
transform: translate(calc(var(--sx) * 45px), calc(var(--sy) * 78px)) scale(1);
|
|
4111
4825
|
transition-delay: .07s;
|
|
4112
4826
|
}
|
|
4113
|
-
.widget:hover .btn-action[data-n="3"] { /*
|
|
4114
|
-
transform: translate(
|
|
4827
|
+
.widget:hover .btn-action[data-n="3"] { /* diagonal near-horizontal */
|
|
4828
|
+
transform: translate(calc(var(--sx) * 78px), calc(var(--sy) * 45px)) scale(1);
|
|
4115
4829
|
transition-delay: .11s;
|
|
4116
4830
|
}
|
|
4117
|
-
.widget:hover .btn-action[data-n="4"] { /*
|
|
4118
|
-
transform: translateX(
|
|
4831
|
+
.widget:hover .btn-action[data-n="4"] { /* horizontal extreme */
|
|
4832
|
+
transform: translateX(calc(var(--sx) * 90px)) scale(1);
|
|
4119
4833
|
transition-delay: .15s;
|
|
4120
4834
|
}
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
left: 50%;
|
|
4125
|
-
top: auto;
|
|
4126
|
-
bottom: calc(100% + 9px);
|
|
4127
|
-
transform: translateX(-50%);
|
|
4128
|
-
}
|
|
4835
|
+
|
|
4836
|
+
/* Per-direction anchors + label sides */
|
|
4837
|
+
${directionBlocks()}
|
|
4129
4838
|
|
|
4130
4839
|
/* \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 */
|
|
4131
4840
|
.rec-badge {
|
|
@@ -4188,7 +4897,18 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4188
4897
|
interceptorsUnsub;
|
|
4189
4898
|
pauseUnsub;
|
|
4190
4899
|
selectorNotFoundUnsub;
|
|
4900
|
+
langUnsub;
|
|
4901
|
+
sessionUnsub;
|
|
4902
|
+
sessionSaveTimer;
|
|
4191
4903
|
controlFirstTimeData = true;
|
|
4904
|
+
// ── draggable widget (spec 007) ──
|
|
4905
|
+
togglePos = null;
|
|
4906
|
+
expandDir = "up-left";
|
|
4907
|
+
dragState;
|
|
4908
|
+
suppressNextToggleClick = false;
|
|
4909
|
+
widgetPointerMove;
|
|
4910
|
+
widgetPointerUp;
|
|
4911
|
+
widgetResize;
|
|
4192
4912
|
_previsualizerRef = null;
|
|
4193
4913
|
httpMonitor;
|
|
4194
4914
|
smartSelectorEnabled = true;
|
|
@@ -4231,17 +4951,31 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4231
4951
|
this.initSubscriptions();
|
|
4232
4952
|
this.render();
|
|
4233
4953
|
this.initVisibility();
|
|
4954
|
+
this.initSessionContinuity();
|
|
4955
|
+
this.initWidgetPosition();
|
|
4234
4956
|
this.keydownHandler = (e) => this.handleKeyboardEvent(e);
|
|
4235
4957
|
window.addEventListener("keydown", this.keydownHandler);
|
|
4958
|
+
this.widgetPointerMove = (e) => this.onWidgetPointerMove(e);
|
|
4959
|
+
this.widgetPointerUp = () => this.onWidgetPointerUp();
|
|
4960
|
+
this.widgetResize = () => this.applyWidgetPosition();
|
|
4961
|
+
window.addEventListener("pointermove", this.widgetPointerMove);
|
|
4962
|
+
window.addEventListener("pointerup", this.widgetPointerUp);
|
|
4963
|
+
window.addEventListener("resize", this.widgetResize);
|
|
4236
4964
|
}
|
|
4237
4965
|
disconnectedCallback() {
|
|
4238
4966
|
if (this._isDisabled) return;
|
|
4239
4967
|
window.removeEventListener("keydown", this.keydownHandler);
|
|
4968
|
+
this.flushActiveSessionOnDisconnect();
|
|
4240
4969
|
this.recordingUnsub?.();
|
|
4241
4970
|
this.commandsUnsub?.();
|
|
4242
4971
|
this.interceptorsUnsub?.();
|
|
4243
4972
|
this.pauseUnsub?.();
|
|
4244
4973
|
this.selectorNotFoundUnsub?.();
|
|
4974
|
+
this.langUnsub?.();
|
|
4975
|
+
this.sessionUnsub?.();
|
|
4976
|
+
if (this.widgetPointerMove) window.removeEventListener("pointermove", this.widgetPointerMove);
|
|
4977
|
+
if (this.widgetPointerUp) window.removeEventListener("pointerup", this.widgetPointerUp);
|
|
4978
|
+
if (this.widgetResize) window.removeEventListener("resize", this.widgetResize);
|
|
4245
4979
|
this.httpMonitor?.uninstall();
|
|
4246
4980
|
this.recording.destroy();
|
|
4247
4981
|
}
|
|
@@ -4264,11 +4998,13 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4264
4998
|
this.isRecording = val;
|
|
4265
4999
|
if (!val && !this.controlFirstTimeData) {
|
|
4266
5000
|
this.saveRecordingHistory();
|
|
5001
|
+
this.clearSessionPersistence();
|
|
4267
5002
|
this.showSaveTestDialog();
|
|
4268
5003
|
}
|
|
4269
5004
|
this.controlFirstTimeData = false;
|
|
4270
5005
|
this.render();
|
|
4271
5006
|
});
|
|
5007
|
+
this.sessionUnsub = this.recording.onSessionChange((state) => this.persistActiveSession(state));
|
|
4272
5008
|
this.commandsUnsub = this.recording.onCommandsChange((cmds) => {
|
|
4273
5009
|
this.cypressCommands = cmds;
|
|
4274
5010
|
if (this._previsualizerRef) this._previsualizerRef.commands = cmds;
|
|
@@ -4284,9 +5020,10 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4284
5020
|
this.selectorNotFoundUnsub = this.recording.onSelectorNotFound((target) => {
|
|
4285
5021
|
if (this.smartSelectorEnabled) this.showSelectorPicker(target);
|
|
4286
5022
|
});
|
|
5023
|
+
this.langUnsub = this.translation.onLangChange(() => this.render());
|
|
4287
5024
|
}
|
|
4288
5025
|
showSelectorPicker(target) {
|
|
4289
|
-
import("./selector-picker-
|
|
5026
|
+
import("./selector-picker-NKYFFFGK.js").then(() => {
|
|
4290
5027
|
const existing = document.querySelector("selector-picker");
|
|
4291
5028
|
if (existing) existing.remove();
|
|
4292
5029
|
const picker = document.createElement("selector-picker");
|
|
@@ -4312,6 +5049,176 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
4312
5049
|
if (strategy) this.recording.selectorStrategy = strategy;
|
|
4313
5050
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
4314
5051
|
}
|
|
5052
|
+
// ── cross-app session continuity (spec 006) ───────────────────────────────
|
|
5053
|
+
/**
|
|
5054
|
+
* On mount, detect a persisted live recording session and either resume it
|
|
5055
|
+
* silently (recent) or prompt continue/discard (stale). No-op when there is
|
|
5056
|
+
* no actively-recording session.
|
|
5057
|
+
*/
|
|
5058
|
+
async initSessionContinuity() {
|
|
5059
|
+
if (this._isDisabled) return;
|
|
5060
|
+
const session = await this.persistence.getActiveSession();
|
|
5061
|
+
if (!session || !session.isRecording) return;
|
|
5062
|
+
const ttlMin = await this.getResumeTtlMinutes();
|
|
5063
|
+
const ageMs = Date.now() - session.updatedAt;
|
|
5064
|
+
if (ageMs <= ttlMin * 6e4) {
|
|
5065
|
+
this.resumeSessionState(session);
|
|
5066
|
+
} else {
|
|
5067
|
+
this.promptResumeOrDiscard(session);
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
async getResumeTtlMinutes() {
|
|
5071
|
+
const config = await this.persistence.getConfig(RESUME_TTL_CONFIG_KEY);
|
|
5072
|
+
const raw = config?.[RESUME_TTL_CONFIG_KEY];
|
|
5073
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
5074
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_RESUME_TTL_MINUTES;
|
|
5075
|
+
}
|
|
5076
|
+
/** Rehydrates the recorder from a persisted session (no bootstrap re-emitted). */
|
|
5077
|
+
resumeSessionState(session) {
|
|
5078
|
+
this.recording.restoreSession(session);
|
|
5079
|
+
this.cypressCommands = session.commands;
|
|
5080
|
+
this.interceptors = session.interceptors;
|
|
5081
|
+
this.controlFirstTimeData = false;
|
|
5082
|
+
this.render();
|
|
5083
|
+
}
|
|
5084
|
+
async promptResumeOrDiscard(session) {
|
|
5085
|
+
const t = this.translation.translate.bind(this.translation);
|
|
5086
|
+
const result = await Swal.fire({
|
|
5087
|
+
title: t("RECORDER.SESSION_RESUME_TITLE"),
|
|
5088
|
+
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>`,
|
|
5089
|
+
showCancelButton: true,
|
|
5090
|
+
confirmButtonText: t("RECORDER.SESSION_CONTINUE_BTN"),
|
|
5091
|
+
cancelButtonText: t("RECORDER.SESSION_DISCARD_BTN"),
|
|
5092
|
+
color: "#e6edf3"
|
|
5093
|
+
});
|
|
5094
|
+
if (result && result.isConfirmed) {
|
|
5095
|
+
this.resumeSessionState(session);
|
|
5096
|
+
} else {
|
|
5097
|
+
this.discardSession();
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
/** Debounced persistence of the live session; clears it the moment recording stops. */
|
|
5101
|
+
persistActiveSession(state) {
|
|
5102
|
+
if (this._isDisabled) return;
|
|
5103
|
+
if (!state.isRecording) {
|
|
5104
|
+
this.clearSessionPersistence();
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
5107
|
+
this.writeSessionBreadcrumb(state);
|
|
5108
|
+
if (this.sessionSaveTimer) clearTimeout(this.sessionSaveTimer);
|
|
5109
|
+
this.sessionSaveTimer = setTimeout(() => {
|
|
5110
|
+
const snap = this.recording.getSessionSnapshot();
|
|
5111
|
+
if (snap.isRecording) this.persistence.saveActiveSession(snap).catch(() => {
|
|
5112
|
+
});
|
|
5113
|
+
}, 300);
|
|
5114
|
+
}
|
|
5115
|
+
writeSessionBreadcrumb(state) {
|
|
5116
|
+
try {
|
|
5117
|
+
localStorage.setItem(ACTIVE_SESSION_BREADCRUMB_KEY, JSON.stringify({
|
|
5118
|
+
sessionId: state.sessionId,
|
|
5119
|
+
isRecording: state.isRecording,
|
|
5120
|
+
updatedAt: state.updatedAt
|
|
5121
|
+
}));
|
|
5122
|
+
} catch {
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
flushActiveSessionOnDisconnect() {
|
|
5126
|
+
const snap = this.recording.getSessionSnapshot();
|
|
5127
|
+
if (!snap.isRecording) return;
|
|
5128
|
+
if (this.sessionSaveTimer) clearTimeout(this.sessionSaveTimer);
|
|
5129
|
+
this.writeSessionBreadcrumb(snap);
|
|
5130
|
+
this.persistence.saveActiveSession(snap).catch(() => {
|
|
5131
|
+
});
|
|
5132
|
+
}
|
|
5133
|
+
clearSessionPersistence() {
|
|
5134
|
+
if (this.sessionSaveTimer) {
|
|
5135
|
+
clearTimeout(this.sessionSaveTimer);
|
|
5136
|
+
this.sessionSaveTimer = void 0;
|
|
5137
|
+
}
|
|
5138
|
+
try {
|
|
5139
|
+
localStorage.removeItem(ACTIVE_SESSION_BREADCRUMB_KEY);
|
|
5140
|
+
} catch {
|
|
5141
|
+
}
|
|
5142
|
+
this.persistence.clearActiveSession().catch(() => {
|
|
5143
|
+
});
|
|
5144
|
+
}
|
|
5145
|
+
/** True when a synchronous breadcrumb marks an active recording session. */
|
|
5146
|
+
hasActiveSession() {
|
|
5147
|
+
try {
|
|
5148
|
+
const raw = localStorage.getItem(ACTIVE_SESSION_BREADCRUMB_KEY);
|
|
5149
|
+
if (!raw) return false;
|
|
5150
|
+
return !!JSON.parse(raw).isRecording;
|
|
5151
|
+
} catch {
|
|
5152
|
+
return false;
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
/** Programmatically resume the persisted session, if any. */
|
|
5156
|
+
resumeSession() {
|
|
5157
|
+
this.persistence.getActiveSession().then((s) => {
|
|
5158
|
+
if (s) this.resumeSessionState(s);
|
|
5159
|
+
});
|
|
5160
|
+
}
|
|
5161
|
+
/** Discard the persisted live session (breadcrumb + DB record). */
|
|
5162
|
+
discardSession() {
|
|
5163
|
+
this.clearSessionPersistence();
|
|
5164
|
+
}
|
|
5165
|
+
// ── draggable widget (spec 007) ────────────────────────────────────────────
|
|
5166
|
+
/** Loads a previously saved widget position and applies it. */
|
|
5167
|
+
async initWidgetPosition() {
|
|
5168
|
+
const config = await this.persistence.getGeneralConfig();
|
|
5169
|
+
const pos = config?.["widgetPosition"];
|
|
5170
|
+
if (pos && typeof pos.x === "number" && typeof pos.y === "number") {
|
|
5171
|
+
this.togglePos = { x: pos.x, y: pos.y };
|
|
5172
|
+
}
|
|
5173
|
+
this.applyWidgetPosition();
|
|
5174
|
+
}
|
|
5175
|
+
/** Positions the `.widget` box from the (clamped) toggle centre and orients the arc. */
|
|
5176
|
+
applyWidgetPosition() {
|
|
5177
|
+
const widget = this.shadow.querySelector(".widget");
|
|
5178
|
+
if (!widget) return;
|
|
5179
|
+
const vw = window.innerWidth;
|
|
5180
|
+
const vh = window.innerHeight;
|
|
5181
|
+
const centre = this.togglePos ?? defaultTogglePosition(vw, vh);
|
|
5182
|
+
const clamped = clampTogglePosition(centre.x, centre.y, vw, vh);
|
|
5183
|
+
this.expandDir = resolveExpandDirection(clamped.x, clamped.y, vw, vh);
|
|
5184
|
+
const topLeft = boxTopLeftFor(clamped, this.expandDir);
|
|
5185
|
+
widget.style.left = `${topLeft.x}px`;
|
|
5186
|
+
widget.style.top = `${topLeft.y}px`;
|
|
5187
|
+
widget.style.right = "auto";
|
|
5188
|
+
widget.style.bottom = "auto";
|
|
5189
|
+
widget.setAttribute("data-expand", this.expandDir);
|
|
5190
|
+
}
|
|
5191
|
+
beginWidgetDrag(e) {
|
|
5192
|
+
const origin = this.togglePos ?? defaultTogglePosition(window.innerWidth, window.innerHeight);
|
|
5193
|
+
this.dragState = { startX: e.clientX, startY: e.clientY, origX: origin.x, origY: origin.y, moved: false };
|
|
5194
|
+
}
|
|
5195
|
+
onWidgetPointerMove(e) {
|
|
5196
|
+
if (!this.dragState) return;
|
|
5197
|
+
const dx = e.clientX - this.dragState.startX;
|
|
5198
|
+
const dy = e.clientY - this.dragState.startY;
|
|
5199
|
+
if (!this.dragState.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
5200
|
+
this.dragState.moved = true;
|
|
5201
|
+
this.togglePos = { x: this.dragState.origX + dx, y: this.dragState.origY + dy };
|
|
5202
|
+
this.applyWidgetPosition();
|
|
5203
|
+
}
|
|
5204
|
+
onWidgetPointerUp() {
|
|
5205
|
+
if (!this.dragState) return;
|
|
5206
|
+
const moved = this.dragState.moved;
|
|
5207
|
+
this.dragState = void 0;
|
|
5208
|
+
if (!moved || !this.togglePos) return;
|
|
5209
|
+
this.suppressNextToggleClick = true;
|
|
5210
|
+
const clamped = clampTogglePosition(this.togglePos.x, this.togglePos.y, window.innerWidth, window.innerHeight);
|
|
5211
|
+
this.togglePos = clamped;
|
|
5212
|
+
this.persistence.setConfig({ widgetPosition: clamped }).catch(() => {
|
|
5213
|
+
});
|
|
5214
|
+
}
|
|
5215
|
+
/** Resets the widget to its default corner and clears the saved position. */
|
|
5216
|
+
resetWidgetPosition() {
|
|
5217
|
+
this.togglePos = null;
|
|
5218
|
+
this.persistence.setConfig({ widgetPosition: null }).catch(() => {
|
|
5219
|
+
});
|
|
5220
|
+
this.applyWidgetPosition();
|
|
5221
|
+
}
|
|
4315
5222
|
toggle() {
|
|
4316
5223
|
this.recording.toggleRecording();
|
|
4317
5224
|
}
|
|
@@ -4628,6 +5535,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4628
5535
|
this.isVisible = !e.detail;
|
|
4629
5536
|
this.style.display = this.isVisible ? "" : "none";
|
|
4630
5537
|
});
|
|
5538
|
+
child.addEventListener("resetwidgetposition", () => this.resetWidgetPosition());
|
|
4631
5539
|
},
|
|
4632
5540
|
willClose: () => {
|
|
4633
5541
|
this.isSettingsDialogOpen = false;
|
|
@@ -4682,7 +5590,8 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4682
5590
|
e.detail.fileName,
|
|
4683
5591
|
e.detail.testId,
|
|
4684
5592
|
e.detail.itBlock,
|
|
4685
|
-
e.detail.interceptorsBlock
|
|
5593
|
+
e.detail.interceptorsBlock,
|
|
5594
|
+
e.detail.notes
|
|
4686
5595
|
), 150);
|
|
4687
5596
|
});
|
|
4688
5597
|
},
|
|
@@ -4696,7 +5605,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4696
5605
|
}, 0);
|
|
4697
5606
|
});
|
|
4698
5607
|
}
|
|
4699
|
-
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "") {
|
|
5608
|
+
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "", notes = "") {
|
|
4700
5609
|
Swal.fire({
|
|
4701
5610
|
title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
|
|
4702
5611
|
html: '<div id="file-editor-modal-content" style="padding:0;height:540px"></div>',
|
|
@@ -4717,6 +5626,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4717
5626
|
child.closeLabel = this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR");
|
|
4718
5627
|
child.itBlock = itBlock;
|
|
4719
5628
|
child.interceptorsBlock = interceptorsBlock;
|
|
5629
|
+
child.notes = notes;
|
|
4720
5630
|
container.appendChild(child);
|
|
4721
5631
|
child.addEventListener("close", () => {
|
|
4722
5632
|
Swal.close();
|
|
@@ -4779,12 +5689,21 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
4779
5689
|
const paused = this.isPaused;
|
|
4780
5690
|
this.style.display = this.isVisible ? "" : "none";
|
|
4781
5691
|
this.shadow.innerHTML = `<style>${getRecorderStyles(rec, paused)}</style>${renderRecorderWidget(rec, paused, this.translation.translate.bind(this.translation))}`;
|
|
4782
|
-
this.shadow.querySelector('[data-action="toggle"]')
|
|
5692
|
+
const toggleBtn = this.shadow.querySelector('[data-action="toggle"]');
|
|
5693
|
+
toggleBtn?.addEventListener("click", () => {
|
|
5694
|
+
if (this.suppressNextToggleClick) {
|
|
5695
|
+
this.suppressNextToggleClick = false;
|
|
5696
|
+
return;
|
|
5697
|
+
}
|
|
5698
|
+
this.toggle();
|
|
5699
|
+
});
|
|
5700
|
+
toggleBtn?.addEventListener("pointerdown", (e) => this.beginWidgetDrag(e));
|
|
4783
5701
|
this.shadow.querySelector('[data-action="pause"]')?.addEventListener("click", () => this.togglePause());
|
|
4784
5702
|
this.shadow.querySelector('[data-action="tests"]')?.addEventListener("click", () => this.showSavedTestsDialog());
|
|
4785
5703
|
this.shadow.querySelector('[data-action="commands"]')?.addEventListener("click", () => this.showCommandsDialog());
|
|
4786
5704
|
this.shadow.querySelector('[data-action="config"]')?.addEventListener("click", () => this.showSettingsDialog());
|
|
4787
5705
|
this.shadow.querySelector('[data-action="browse"]')?.addEventListener("click", () => this.showAdvancedEditorDialog());
|
|
5706
|
+
this.applyWidgetPosition();
|
|
4788
5707
|
}
|
|
4789
5708
|
};
|
|
4790
5709
|
if (!customElements.get("lib-e2e-recorder")) {
|
|
@@ -4792,37 +5711,52 @@ if (!customElements.get("lib-e2e-recorder")) {
|
|
|
4792
5711
|
}
|
|
4793
5712
|
|
|
4794
5713
|
// src/index.ts
|
|
4795
|
-
var VERSION = "0.
|
|
5714
|
+
var VERSION = "0.6.0";
|
|
4796
5715
|
export {
|
|
5716
|
+
ACTIVE_SESSION_BREADCRUMB_KEY,
|
|
4797
5717
|
AdvancedTestEditorElement,
|
|
4798
5718
|
AdvancedTestTransformationService,
|
|
4799
5719
|
ConfigurationElement,
|
|
4800
5720
|
DB_SCHEMA,
|
|
4801
5721
|
DB_STORE_NAMES,
|
|
5722
|
+
DEFAULT_RESUME_TTL_MINUTES,
|
|
5723
|
+
DRAG_THRESHOLD,
|
|
4802
5724
|
FilePreviewElement,
|
|
4803
5725
|
HttpMonitor,
|
|
4804
5726
|
INPUT_TYPES,
|
|
4805
5727
|
LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
|
|
5728
|
+
LOCALE_BY_LANG,
|
|
4806
5729
|
LibE2eRecorderElement,
|
|
4807
5730
|
PersistenceService,
|
|
5731
|
+
RESUME_TTL_CONFIG_KEY,
|
|
4808
5732
|
RecordingService,
|
|
4809
5733
|
SCROLLBAR_STYLES,
|
|
4810
5734
|
SUPPORTED_LANGS,
|
|
4811
5735
|
SaveTestElement,
|
|
5736
|
+
SelectorPickerElement,
|
|
4812
5737
|
Subject,
|
|
5738
|
+
TOGGLE_MARGIN,
|
|
4813
5739
|
TestEditorElement,
|
|
4814
5740
|
TestPrevisualizerElement,
|
|
4815
5741
|
TransformationService,
|
|
4816
5742
|
TranslationService,
|
|
4817
5743
|
VERSION,
|
|
4818
5744
|
advancedTestTransformationService,
|
|
5745
|
+
boxOffsetForDirection,
|
|
5746
|
+
boxTopLeftFor,
|
|
5747
|
+
clampTogglePosition,
|
|
5748
|
+
defaultTogglePosition,
|
|
4819
5749
|
generateAlias,
|
|
4820
5750
|
injectStyles,
|
|
4821
5751
|
isLang,
|
|
5752
|
+
isLocalHost,
|
|
5753
|
+
localeForLang,
|
|
4822
5754
|
makeModalResizable,
|
|
4823
5755
|
makeSwalDraggable,
|
|
4824
5756
|
makeSwalDraggableByContentId,
|
|
4825
5757
|
persistenceService,
|
|
5758
|
+
resolveExpandDirection,
|
|
5759
|
+
selectTestsForExport,
|
|
4826
5760
|
setSwal2DataCyAttribute,
|
|
4827
5761
|
showToast,
|
|
4828
5762
|
transformationService,
|