lib-e2e-cypress-for-dummys-ts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4355 @@
1
+ import {
2
+ FORBIDDEN_ID_PREFIXES,
3
+ escAttr,
4
+ escHtml
5
+ } from "./chunk-DRNRKHXN.js";
6
+
7
+ // src/models/lang.model.ts
8
+ var SUPPORTED_LANGS = ["es", "en", "fr", "it", "de"];
9
+ function isLang(value) {
10
+ return SUPPORTED_LANGS.includes(value);
11
+ }
12
+
13
+ // src/models/input-types.model.ts
14
+ var INPUT_TYPES = [
15
+ "text",
16
+ "password",
17
+ "email",
18
+ "search",
19
+ "tel",
20
+ "url",
21
+ "number",
22
+ "textarea"
23
+ ];
24
+
25
+ // src/models/db-schema.model.ts
26
+ var DB_SCHEMA = {
27
+ name: "E2ECypressDB",
28
+ version: 10,
29
+ stores: [
30
+ {
31
+ name: "tests",
32
+ keyPath: "id",
33
+ autoIncrement: true,
34
+ indexes: [
35
+ { name: "name", keyPath: "name", unique: false },
36
+ { name: "createdAt", keyPath: "createdAt", unique: false }
37
+ ]
38
+ },
39
+ {
40
+ name: "commands",
41
+ keyPath: "id",
42
+ autoIncrement: true,
43
+ indexes: [
44
+ { name: "command", keyPath: "command", unique: false },
45
+ { name: "testId", keyPath: "testId", unique: false },
46
+ { name: "createdAt", keyPath: "createdAt", unique: false }
47
+ ]
48
+ },
49
+ {
50
+ name: "interceptors",
51
+ keyPath: "id",
52
+ autoIncrement: true,
53
+ indexes: [
54
+ { name: "interceptor", keyPath: "interceptor", unique: false },
55
+ { name: "testId", keyPath: "testId", unique: false },
56
+ { name: "createdAt", keyPath: "createdAt", unique: false }
57
+ ]
58
+ },
59
+ {
60
+ name: "configuration",
61
+ keyPath: "id",
62
+ autoIncrement: true,
63
+ indexes: [
64
+ { name: "language", keyPath: "language", unique: false },
65
+ { name: "extendedHttpCommands", keyPath: "extendedHttpCommands", unique: false },
66
+ { name: "allowReadWriteFiles", keyPath: "allowReadWriteFiles", unique: false }
67
+ ]
68
+ }
69
+ ]
70
+ };
71
+ var DB_STORE_NAMES = DB_SCHEMA.stores.map((s) => s.name);
72
+
73
+ // src/i18n/es.ts
74
+ var I18N_ES = {
75
+ MAIN_FRAME: {
76
+ SETTINGS: "Configuraci\xF3n",
77
+ SHOW_ADVANCED_EDITOR: "Editor avanzado",
78
+ DIALOG_SAVED_TESTS: "Pruebas guardadas",
79
+ DIALOG_COMMANDS: "Previsualizador de comandos Cypress",
80
+ DIALOG_SAVE: "\xBFGuardar la grabaci\xF3n?"
81
+ },
82
+ SAVE_TEST: {
83
+ DESC_LABEL: "Descripci\xF3n de la prueba",
84
+ CANCEL: "Cancelar",
85
+ ASK: "\xBFGuardar el test grabado?",
86
+ YES_CONTINUE: "S\xED, continuar",
87
+ NO_DISCARD: "No, descartar",
88
+ DESC_PLACEHOLDER: "Ej: Login correcto con credenciales v\xE1lidas",
89
+ TAGS_LABEL: "Etiquetas (opcional, para filtrar despu\xE9s):",
90
+ TAGS_PLACEHOLDER: "smoke, login, regresi\xF3n\u2026",
91
+ ADD_TAG: "+ A\xF1adir",
92
+ NO_TAGS: "Sin etiquetas",
93
+ SAVE_BTN: "\u{1F4BE} Guardar",
94
+ SAVE_AND_EDIT: "\u{1F4DD} Guardar y editar",
95
+ REMOVE_TAG_TITLE: "Quitar"
96
+ },
97
+ TEST_EDITOR: {
98
+ NO_TAGS: "Sin etiquetas",
99
+ SELECTED_SINGULAR: "seleccionado",
100
+ SELECTED_PLURAL: "seleccionados",
101
+ DESCRIBE_PLACEHOLDER: "Nombre del describe (opcional)",
102
+ COPY_DESCRIBE: "\u{1F5C2} Copiar describe()",
103
+ MULTI_SELECT: "\u{1F5C2} Multi-select",
104
+ CANCEL_SELECT: "\u2715 Cancelar",
105
+ SECTION_COMMANDS: "Comandos",
106
+ SECTION_INTERCEPTORS: "Interceptores",
107
+ COPY_CMDS_BTN: "\u{1F4CB} Copiar comandos",
108
+ COPY_ICPS_BTN: "\u{1F4CB} Copiar interceptores",
109
+ DEFAULT_DESCRIBE: "Suite de tests",
110
+ NO_TESTS: "No hay tests guardados",
111
+ DELETE_TITLE: "Eliminar"
112
+ },
113
+ CONFIG: {
114
+ LANG_SECTION: "\u{1F310} Idioma",
115
+ LANG_FIELD: "Interfaz",
116
+ HTTP_SECTION: "\u26A1 HTTP Avanzado",
117
+ HTTP_TITLE: "Validaciones de body",
118
+ HTTP_SUB: "GET \u2192 response \xB7 POST/PUT \u2192 request",
119
+ SELECTOR_SECTION: "\u{1F3AF} Estrategia de selector",
120
+ SELECTOR_LABEL: "Atributo preferido para cy.get()",
121
+ SELECTOR_OPT_DATACY: "data-cy (por defecto)",
122
+ SELECTOR_OPT_TESTID: "data-testid",
123
+ SELECTOR_OPT_ARIA: "aria-label",
124
+ SELECTOR_OPT_ID: "id (#elemento)",
125
+ SELECTOR_HINT: "El grabador buscar\xE1 este atributo primero. Si no existe, intentar\xE1 los dem\xE1s.",
126
+ FOLDER_SECTION: "\u{1F4C1} Carpeta Cypress",
127
+ FOLDER_CONNECTED: "conectado \u2014",
128
+ FOLDER_NOT_SET: "sin configurar",
129
+ FOLDER_CHANGE_BTN: "\u{1F4C1} Cambiar carpeta",
130
+ FOLDER_SELECT_BTN: "\u{1F4C1} Seleccionar carpeta",
131
+ FOLDER_REVOKE_BTN: "\u2715 Quitar acceso",
132
+ DATA_SECTION: "\u{1F4BE} Datos",
133
+ DATA_DESC: "Exporta todos tus tests a JSON o importa una copia de seguridad.",
134
+ EXPORT_BTN: "\u2B06\uFE0F Exportar tests",
135
+ IMPORT_BTN: "\u2B07\uFE0F Importar tests",
136
+ FOLDER_UPDATED_TOAST: "\u2713 Carpeta de Cypress actualizada",
137
+ FOLDER_ERROR_TOAST: "Error al acceder a la carpeta",
138
+ IMPORT_SUCCESS: "Datos importados correctamente.",
139
+ IMPORT_ERROR: "Error al importar.",
140
+ JSON_INVALID: "El archivo no es un JSON v\xE1lido.",
141
+ JSON_BAD_FORMAT: "El archivo no tiene el formato esperado.",
142
+ SMART_SELECTOR_SECTION: "\u{1F50D} Selector inteligente",
143
+ SMART_SELECTOR_TITLE: "Selector inteligente",
144
+ SMART_SELECTOR_SUB: "Muestra un picker al hacer click en elementos sin selector v\xE1lido."
145
+ },
146
+ SELECTOR_PICKER: {
147
+ TITLE: "Selecciona un elemento del DOM",
148
+ PREVIEW_LABEL: "navegar",
149
+ CONFIRM_HINT: "confirmar",
150
+ CANCEL_HINT: "cancelar",
151
+ QUALITY_EXCELLENT: "Excelente \u2014 tiene atributo de prueba (data-cy / data-testid / aria-label)",
152
+ QUALITY_GOOD: "Bueno \u2014 tiene un id v\xE1lido",
153
+ QUALITY_ACCEPTABLE: "Aceptable \u2014 solo tiene clases CSS",
154
+ QUALITY_POOR: "No recomendado \u2014 solo tag o estilo inline"
155
+ },
156
+ TEST_PREVISUALIZER: {
157
+ INTERCEPTORS: "Interceptores",
158
+ COPY_CMDS_BTN: "\u{1F4CB} Copiar comandos",
159
+ SHOW_INTERCEPTORS: "\u25BC Ver interceptores",
160
+ HIDE_INTERCEPTORS: "\u25B2 Ocultar interceptores",
161
+ SECTION_COMMANDS: "Comandos",
162
+ NO_CMDS_YET: "Sin comandos a\xFAn",
163
+ NO_ICP_SHORT: "Sin interceptores",
164
+ COPY_ICP_BTN: "\u{1F4CB} Copiar interceptores",
165
+ UP_TITLE: "Subir",
166
+ DOWN_TITLE: "Bajar",
167
+ DEL_TITLE: "Eliminar"
168
+ },
169
+ ADVANCED_EDITOR: {
170
+ REAUTH_BTN: "\u{1F513} Reactivar acceso",
171
+ PERMISSION_EXPIRED: "\u{1F511} El permiso ha caducado",
172
+ PERMISSION_EXPIRED_HINT: "Haz clic para reactivar el acceso a tu carpeta de Cypress.",
173
+ NO_ACCESS: "\u{1F512} Sin acceso a archivos locales",
174
+ NO_ACCESS_HINT: "Configura el acceso en \u2699\uFE0F Config.",
175
+ NO_FILES: "Sin archivos",
176
+ SELECT_FILE: "\u2190 Selecciona un archivo del \xE1rbol",
177
+ IT_LABEL: "\u{1F9EA} it() a insertar:",
178
+ COPY_IT_BTN: "\u{1F4CB} Copiar it()",
179
+ BEFORE_EACH_LABEL: "\u{1F500} beforeEach() interceptores:",
180
+ COPY_ICP_BTN: "\u{1F4CB} Copiar interceptores",
181
+ INSERT_BTN: "\u{1F4BE} Insertar en archivo",
182
+ EDIT_MANUAL_BTN: "\u270F\uFE0F Editar manualmente",
183
+ CLOSE_BTN: "\u2715 Cerrar"
184
+ },
185
+ FILE_PREVIEW: {
186
+ NO_FILE: "Sin archivo",
187
+ COPY_TITLE: "Copiar contenido del editor",
188
+ BLOCK_IT: "\u{1F9EA} it()",
189
+ COPY_BTN: "\u{1F4CB} Copiar",
190
+ BLOCK_BEFORE_EACH: "\u{1F500} beforeEach()",
191
+ NO_CHANGES: "Sin cambios respecto al original",
192
+ NO_CHANGES_SHORT: "Sin cambios",
193
+ LAUNCH_BTN: "\u25B6 Lanzar test",
194
+ DIFF_BTN: "\u{1F4CA} Diff",
195
+ SAVE_BTN: "\u{1F4BE} Guardar",
196
+ CLOSE_BTN: "\u2715 Cerrar",
197
+ BACK_TO_EDITOR: "\u2190 Volver al editor"
198
+ },
199
+ RECORDER: {
200
+ FS_TITLE: "\u{1F4C1} Acceso a ficheros",
201
+ FS_LATER_BTN: "Ahora no",
202
+ FS_SELECT_BTN: "\u{1F4C1} Seleccionar carpeta",
203
+ FS_SUCCESS_TOAST: "\u2713 Carpeta de Cypress configurada",
204
+ FS_ERROR_TOAST: "Error al acceder a la carpeta",
205
+ FS_INTRO_HTML: 'El <strong style="color:#e6edf3">editor avanzado</strong> puede insertar tus tests directamente en los archivos <code style="background:#0d1117;padding:1px 6px;border-radius:4px;font-size:11px;color:#c9d1d9">.cy.ts</code> de Cypress.',
206
+ FS_STRUCTURE_HINT_HTML: 'Selecciona la carpeta que contiene la subcarpeta <code style="background:#0d1117;padding:1px 5px;border-radius:4px;font-size:10px;color:#c9d1d9">e2e/</code>. La extensi\xF3n espera esta estructura:',
207
+ FS_TREE_PICK_HINT: "\u2190 selecciona esta carpeta",
208
+ FS_TREE_READ_HINT: "\u2190 la extensi\xF3n leer\xE1 aqu\xED",
209
+ FS_PERMISSION_NOTE: "El permiso se guarda en el navegador y no se vuelve a solicitar.",
210
+ ASSERTION_SECTION: "\u2795 A\xF1adir aserci\xF3n",
211
+ ASSERT_SELECTOR_PH: 'selector, ej: [data-cy="btn"]',
212
+ ASSERT_VALUE_PH: "valor esperado",
213
+ ASSERT_ADD_BTN: "\u2795 A\xF1adir",
214
+ FILE_EDITOR_TITLE: "\u270F\uFE0F Editor de archivo",
215
+ FILE_SAVED_TOAST: "\u2713 Archivo guardado",
216
+ FILE_SAVE_ERROR_TOAST: "Error al guardar el archivo",
217
+ BTN_CONFIG: "Config",
218
+ BTN_FILES: "Ficheros",
219
+ BTN_COMMANDS: "Comandos",
220
+ BTN_TESTS: "Tests",
221
+ PAUSE_TITLE: "Pausar (Ctrl+P)",
222
+ RESUME_TITLE: "Reanudar (Ctrl+P)",
223
+ BADGE_PAUSED: "\u23F8 PAUSA",
224
+ BADGE_REC: "\u25CF REC",
225
+ STOP_TITLE: "Detener (Ctrl+R)",
226
+ START_TITLE: "Grabar (Ctrl+R)"
227
+ }
228
+ };
229
+
230
+ // src/i18n/en.ts
231
+ var I18N_EN = {
232
+ MAIN_FRAME: {
233
+ SETTINGS: "Settings",
234
+ SHOW_ADVANCED_EDITOR: "Advanced editor",
235
+ DIALOG_SAVED_TESTS: "Saved tests",
236
+ DIALOG_COMMANDS: "Cypress commands previewer",
237
+ DIALOG_SAVE: "Save recording?"
238
+ },
239
+ SAVE_TEST: {
240
+ DESC_LABEL: "Test description",
241
+ CANCEL: "Cancel",
242
+ ASK: "Save the recorded test?",
243
+ YES_CONTINUE: "Yes, continue",
244
+ NO_DISCARD: "No, discard",
245
+ DESC_PLACEHOLDER: "E.g.: Successful login with valid credentials",
246
+ TAGS_LABEL: "Tags (optional, for later filtering):",
247
+ TAGS_PLACEHOLDER: "smoke, login, regression\u2026",
248
+ ADD_TAG: "+ Add",
249
+ NO_TAGS: "No tags",
250
+ SAVE_BTN: "\u{1F4BE} Save",
251
+ SAVE_AND_EDIT: "\u{1F4DD} Save and edit",
252
+ REMOVE_TAG_TITLE: "Remove"
253
+ },
254
+ TEST_EDITOR: {
255
+ NO_TAGS: "No tags",
256
+ SELECTED_SINGULAR: "selected",
257
+ SELECTED_PLURAL: "selected",
258
+ DESCRIBE_PLACEHOLDER: "describe name (optional)",
259
+ COPY_DESCRIBE: "\u{1F5C2} Copy describe()",
260
+ MULTI_SELECT: "\u{1F5C2} Multi-select",
261
+ CANCEL_SELECT: "\u2715 Cancel",
262
+ SECTION_COMMANDS: "Commands",
263
+ SECTION_INTERCEPTORS: "Interceptors",
264
+ COPY_CMDS_BTN: "\u{1F4CB} Copy commands",
265
+ COPY_ICPS_BTN: "\u{1F4CB} Copy interceptors",
266
+ DEFAULT_DESCRIBE: "Test suite",
267
+ NO_TESTS: "No saved tests",
268
+ DELETE_TITLE: "Delete"
269
+ },
270
+ CONFIG: {
271
+ LANG_SECTION: "\u{1F310} Language",
272
+ LANG_FIELD: "Interface",
273
+ HTTP_SECTION: "\u26A1 Advanced HTTP",
274
+ HTTP_TITLE: "Body validations",
275
+ HTTP_SUB: "GET \u2192 response \xB7 POST/PUT \u2192 request",
276
+ SELECTOR_SECTION: "\u{1F3AF} Selector strategy",
277
+ SELECTOR_LABEL: "Preferred attribute for cy.get()",
278
+ SELECTOR_OPT_DATACY: "data-cy (default)",
279
+ SELECTOR_OPT_TESTID: "data-testid",
280
+ SELECTOR_OPT_ARIA: "aria-label",
281
+ SELECTOR_OPT_ID: "id (#element)",
282
+ SELECTOR_HINT: "The recorder will look for this attribute first. If not found, it will try the others.",
283
+ FOLDER_SECTION: "\u{1F4C1} Cypress Folder",
284
+ FOLDER_CONNECTED: "connected \u2014",
285
+ FOLDER_NOT_SET: "not configured",
286
+ FOLDER_CHANGE_BTN: "\u{1F4C1} Change folder",
287
+ FOLDER_SELECT_BTN: "\u{1F4C1} Select folder",
288
+ FOLDER_REVOKE_BTN: "\u2715 Revoke access",
289
+ DATA_SECTION: "\u{1F4BE} Data",
290
+ DATA_DESC: "Export all your tests to JSON or import a backup.",
291
+ EXPORT_BTN: "\u2B06\uFE0F Export tests",
292
+ IMPORT_BTN: "\u2B07\uFE0F Import tests",
293
+ FOLDER_UPDATED_TOAST: "\u2713 Cypress folder updated",
294
+ FOLDER_ERROR_TOAST: "Error accessing the folder",
295
+ IMPORT_SUCCESS: "Data imported successfully.",
296
+ IMPORT_ERROR: "Error importing.",
297
+ JSON_INVALID: "The file is not valid JSON.",
298
+ JSON_BAD_FORMAT: "The file does not have the expected format.",
299
+ SMART_SELECTOR_SECTION: "\u{1F50D} Smart selector",
300
+ SMART_SELECTOR_TITLE: "Smart selector",
301
+ SMART_SELECTOR_SUB: "Shows a picker when clicking elements with no valid selector."
302
+ },
303
+ SELECTOR_PICKER: {
304
+ TITLE: "Select a DOM element",
305
+ PREVIEW_LABEL: "navigate",
306
+ CONFIRM_HINT: "confirm",
307
+ CANCEL_HINT: "cancel",
308
+ QUALITY_EXCELLENT: "Excellent \u2014 has a testing attribute (data-cy / data-testid / aria-label)",
309
+ QUALITY_GOOD: "Good \u2014 has a valid id",
310
+ QUALITY_ACCEPTABLE: "Acceptable \u2014 CSS classes only",
311
+ QUALITY_POOR: "Not recommended \u2014 tag name or inline style only"
312
+ },
313
+ TEST_PREVISUALIZER: {
314
+ INTERCEPTORS: "Interceptors",
315
+ COPY_CMDS_BTN: "\u{1F4CB} Copy commands",
316
+ SHOW_INTERCEPTORS: "\u25BC Show interceptors",
317
+ HIDE_INTERCEPTORS: "\u25B2 Hide interceptors",
318
+ SECTION_COMMANDS: "Commands",
319
+ NO_CMDS_YET: "No commands yet",
320
+ NO_ICP_SHORT: "No interceptors",
321
+ COPY_ICP_BTN: "\u{1F4CB} Copy interceptors",
322
+ UP_TITLE: "Move up",
323
+ DOWN_TITLE: "Move down",
324
+ DEL_TITLE: "Delete"
325
+ },
326
+ ADVANCED_EDITOR: {
327
+ REAUTH_BTN: "\u{1F513} Reactivate access",
328
+ PERMISSION_EXPIRED: "\u{1F511} Permission expired",
329
+ PERMISSION_EXPIRED_HINT: "Click to reactivate access to your Cypress folder.",
330
+ NO_ACCESS: "\u{1F512} No access to local files",
331
+ NO_ACCESS_HINT: "Configure access in \u2699\uFE0F Config.",
332
+ NO_FILES: "No files",
333
+ SELECT_FILE: "\u2190 Select a file from the tree",
334
+ IT_LABEL: "\u{1F9EA} it() to insert:",
335
+ COPY_IT_BTN: "\u{1F4CB} Copy it()",
336
+ BEFORE_EACH_LABEL: "\u{1F500} beforeEach() interceptors:",
337
+ COPY_ICP_BTN: "\u{1F4CB} Copy interceptors",
338
+ INSERT_BTN: "\u{1F4BE} Insert into file",
339
+ EDIT_MANUAL_BTN: "\u270F\uFE0F Edit manually",
340
+ CLOSE_BTN: "\u2715 Close"
341
+ },
342
+ FILE_PREVIEW: {
343
+ NO_FILE: "No file",
344
+ COPY_TITLE: "Copy editor content",
345
+ BLOCK_IT: "\u{1F9EA} it()",
346
+ COPY_BTN: "\u{1F4CB} Copy",
347
+ BLOCK_BEFORE_EACH: "\u{1F500} beforeEach()",
348
+ NO_CHANGES: "No changes from original",
349
+ NO_CHANGES_SHORT: "No changes",
350
+ LAUNCH_BTN: "\u25B6 Run test",
351
+ DIFF_BTN: "\u{1F4CA} Diff",
352
+ SAVE_BTN: "\u{1F4BE} Save",
353
+ CLOSE_BTN: "\u2715 Close",
354
+ BACK_TO_EDITOR: "\u2190 Back to editor"
355
+ },
356
+ RECORDER: {
357
+ FS_TITLE: "\u{1F4C1} File access",
358
+ FS_LATER_BTN: "Not now",
359
+ FS_SELECT_BTN: "\u{1F4C1} Select folder",
360
+ FS_SUCCESS_TOAST: "\u2713 Cypress folder configured",
361
+ FS_ERROR_TOAST: "Error accessing the folder",
362
+ FS_INTRO_HTML: 'The <strong style="color:#e6edf3">advanced editor</strong> can insert your tests directly into the Cypress <code style="background:#0d1117;padding:1px 6px;border-radius:4px;font-size:11px;color:#c9d1d9">.cy.ts</code> files.',
363
+ FS_STRUCTURE_HINT_HTML: 'Select the folder that contains the <code style="background:#0d1117;padding:1px 5px;border-radius:4px;font-size:10px;color:#c9d1d9">e2e/</code> subfolder. The extension expects this structure:',
364
+ FS_TREE_PICK_HINT: "\u2190 select this folder",
365
+ FS_TREE_READ_HINT: "\u2190 the extension will read here",
366
+ FS_PERMISSION_NOTE: "The permission is stored in the browser and will not be requested again.",
367
+ ASSERTION_SECTION: "\u2795 Add assertion",
368
+ ASSERT_SELECTOR_PH: 'selector, e.g.: [data-cy="btn"]',
369
+ ASSERT_VALUE_PH: "expected value",
370
+ ASSERT_ADD_BTN: "\u2795 Add",
371
+ FILE_EDITOR_TITLE: "\u270F\uFE0F File editor",
372
+ FILE_SAVED_TOAST: "\u2713 File saved",
373
+ FILE_SAVE_ERROR_TOAST: "Error saving the file",
374
+ BTN_CONFIG: "Config",
375
+ BTN_FILES: "Files",
376
+ BTN_COMMANDS: "Commands",
377
+ BTN_TESTS: "Tests",
378
+ PAUSE_TITLE: "Pause (Ctrl+P)",
379
+ RESUME_TITLE: "Resume (Ctrl+P)",
380
+ BADGE_PAUSED: "\u23F8 PAUSED",
381
+ BADGE_REC: "\u25CF REC",
382
+ STOP_TITLE: "Stop (Ctrl+R)",
383
+ START_TITLE: "Record (Ctrl+R)"
384
+ }
385
+ };
386
+
387
+ // src/i18n/fr.ts
388
+ var I18N_FR = {
389
+ MAIN_FRAME: {
390
+ SETTINGS: "Param\xE8tres",
391
+ SHOW_ADVANCED_EDITOR: "\xC9diteur avanc\xE9",
392
+ DIALOG_SAVED_TESTS: "Tests enregistr\xE9s",
393
+ DIALOG_COMMANDS: "Aper\xE7u des commandes Cypress",
394
+ DIALOG_SAVE: "Enregistrer l'enregistrement ?"
395
+ },
396
+ SAVE_TEST: {
397
+ DESC_LABEL: "Description du test",
398
+ CANCEL: "Annuler",
399
+ ASK: "Enregistrer le test ?",
400
+ YES_CONTINUE: "Oui, continuer",
401
+ NO_DISCARD: "Non, ignorer",
402
+ DESC_PLACEHOLDER: "Ex : Connexion r\xE9ussie avec identifiants valides",
403
+ TAGS_LABEL: "\xC9tiquettes (optionnel, pour filtrer ensuite) :",
404
+ TAGS_PLACEHOLDER: "smoke, login, r\xE9gression\u2026",
405
+ ADD_TAG: "+ Ajouter",
406
+ NO_TAGS: "Sans \xE9tiquettes",
407
+ SAVE_BTN: "\u{1F4BE} Enregistrer",
408
+ SAVE_AND_EDIT: "\u{1F4DD} Enregistrer et \xE9diter",
409
+ REMOVE_TAG_TITLE: "Retirer"
410
+ },
411
+ TEST_EDITOR: {
412
+ NO_TAGS: "Sans \xE9tiquettes",
413
+ SELECTED_SINGULAR: "s\xE9lectionn\xE9",
414
+ SELECTED_PLURAL: "s\xE9lectionn\xE9s",
415
+ DESCRIBE_PLACEHOLDER: "Nom du describe (optionnel)",
416
+ COPY_DESCRIBE: "\u{1F5C2} Copier describe()",
417
+ MULTI_SELECT: "\u{1F5C2} Multi-s\xE9lection",
418
+ CANCEL_SELECT: "\u2715 Annuler",
419
+ SECTION_COMMANDS: "Commandes",
420
+ SECTION_INTERCEPTORS: "Intercepteurs",
421
+ COPY_CMDS_BTN: "\u{1F4CB} Copier les commandes",
422
+ COPY_ICPS_BTN: "\u{1F4CB} Copier les intercepteurs",
423
+ DEFAULT_DESCRIBE: "Suite de tests",
424
+ NO_TESTS: "Aucun test enregistr\xE9",
425
+ DELETE_TITLE: "Supprimer"
426
+ },
427
+ CONFIG: {
428
+ LANG_SECTION: "\u{1F310} Langue",
429
+ LANG_FIELD: "Interface",
430
+ HTTP_SECTION: "\u26A1 HTTP Avanc\xE9",
431
+ HTTP_TITLE: "Validations du corps",
432
+ HTTP_SUB: "GET \u2192 r\xE9ponse \xB7 POST/PUT \u2192 requ\xEAte",
433
+ SELECTOR_SECTION: "\u{1F3AF} Strat\xE9gie de s\xE9lecteur",
434
+ SELECTOR_LABEL: "Attribut pr\xE9f\xE9r\xE9 pour cy.get()",
435
+ SELECTOR_OPT_DATACY: "data-cy (par d\xE9faut)",
436
+ SELECTOR_OPT_TESTID: "data-testid",
437
+ SELECTOR_OPT_ARIA: "aria-label",
438
+ SELECTOR_OPT_ID: "id (#\xE9l\xE9ment)",
439
+ SELECTOR_HINT: "L'enregistreur cherchera d'abord cet attribut. Si absent, il essaiera les autres.",
440
+ FOLDER_SECTION: "\u{1F4C1} Dossier Cypress",
441
+ FOLDER_CONNECTED: "connect\xE9 \u2014",
442
+ FOLDER_NOT_SET: "non configur\xE9",
443
+ FOLDER_CHANGE_BTN: "\u{1F4C1} Changer de dossier",
444
+ FOLDER_SELECT_BTN: "\u{1F4C1} S\xE9lectionner un dossier",
445
+ FOLDER_REVOKE_BTN: "\u2715 R\xE9voquer l'acc\xE8s",
446
+ DATA_SECTION: "\u{1F4BE} Donn\xE9es",
447
+ DATA_DESC: "Exportez tous vos tests en JSON ou importez une sauvegarde.",
448
+ EXPORT_BTN: "\u2B06\uFE0F Exporter les tests",
449
+ IMPORT_BTN: "\u2B07\uFE0F Importer les tests",
450
+ FOLDER_UPDATED_TOAST: "\u2713 Dossier Cypress mis \xE0 jour",
451
+ FOLDER_ERROR_TOAST: "Erreur lors de l'acc\xE8s au dossier",
452
+ IMPORT_SUCCESS: "Donn\xE9es import\xE9es avec succ\xE8s.",
453
+ IMPORT_ERROR: "Erreur lors de l'importation.",
454
+ JSON_INVALID: "Le fichier n'est pas un JSON valide.",
455
+ JSON_BAD_FORMAT: "Le fichier n'a pas le format attendu.",
456
+ SMART_SELECTOR_SECTION: "\u{1F50D} S\xE9lecteur intelligent",
457
+ SMART_SELECTOR_TITLE: "S\xE9lecteur intelligent",
458
+ SMART_SELECTOR_SUB: "Affiche un s\xE9lecteur lors du clic sur des \xE9l\xE9ments sans s\xE9lecteur valide."
459
+ },
460
+ SELECTOR_PICKER: {
461
+ TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
462
+ PREVIEW_LABEL: "naviguer",
463
+ CONFIRM_HINT: "confirmer",
464
+ CANCEL_HINT: "annuler",
465
+ QUALITY_EXCELLENT: "Excellent \u2014 attribut de test pr\xE9sent (data-cy / data-testid / aria-label)",
466
+ QUALITY_GOOD: "Bon \u2014 id valide",
467
+ QUALITY_ACCEPTABLE: "Acceptable \u2014 classes CSS uniquement",
468
+ QUALITY_POOR: "D\xE9conseill\xE9 \u2014 seulement le nom de balise ou style inline"
469
+ },
470
+ TEST_PREVISUALIZER: {
471
+ INTERCEPTORS: "Intercepteurs",
472
+ COPY_CMDS_BTN: "\u{1F4CB} Copier les commandes",
473
+ SHOW_INTERCEPTORS: "\u25BC Voir les intercepteurs",
474
+ HIDE_INTERCEPTORS: "\u25B2 Masquer les intercepteurs",
475
+ SECTION_COMMANDS: "Commandes",
476
+ NO_CMDS_YET: "Aucune commande encore",
477
+ NO_ICP_SHORT: "Aucun intercepteur",
478
+ COPY_ICP_BTN: "\u{1F4CB} Copier les intercepteurs",
479
+ UP_TITLE: "Monter",
480
+ DOWN_TITLE: "Descendre",
481
+ DEL_TITLE: "Supprimer"
482
+ },
483
+ ADVANCED_EDITOR: {
484
+ REAUTH_BTN: "\u{1F513} R\xE9activer l'acc\xE8s",
485
+ PERMISSION_EXPIRED: "\u{1F511} Autorisation expir\xE9e",
486
+ PERMISSION_EXPIRED_HINT: "Cliquez pour r\xE9activer l'acc\xE8s \xE0 votre dossier Cypress.",
487
+ NO_ACCESS: "\u{1F512} Pas d'acc\xE8s aux fichiers locaux",
488
+ NO_ACCESS_HINT: "Configurez l'acc\xE8s dans \u2699\uFE0F Config.",
489
+ NO_FILES: "Aucun fichier",
490
+ SELECT_FILE: "\u2190 S\xE9lectionnez un fichier dans l'arborescence",
491
+ IT_LABEL: "\u{1F9EA} it() \xE0 ins\xE9rer :",
492
+ COPY_IT_BTN: "\u{1F4CB} Copier it()",
493
+ BEFORE_EACH_LABEL: "\u{1F500} beforeEach() intercepteurs :",
494
+ COPY_ICP_BTN: "\u{1F4CB} Copier les intercepteurs",
495
+ INSERT_BTN: "\u{1F4BE} Ins\xE9rer dans le fichier",
496
+ EDIT_MANUAL_BTN: "\u270F\uFE0F \xC9diter manuellement",
497
+ CLOSE_BTN: "\u2715 Fermer"
498
+ },
499
+ FILE_PREVIEW: {
500
+ NO_FILE: "Sans fichier",
501
+ COPY_TITLE: "Copier le contenu de l'\xE9diteur",
502
+ BLOCK_IT: "\u{1F9EA} it()",
503
+ COPY_BTN: "\u{1F4CB} Copier",
504
+ BLOCK_BEFORE_EACH: "\u{1F500} beforeEach()",
505
+ NO_CHANGES: "Aucun changement par rapport \xE0 l'original",
506
+ NO_CHANGES_SHORT: "Aucun changement",
507
+ LAUNCH_BTN: "\u25B6 Lancer le test",
508
+ DIFF_BTN: "\u{1F4CA} Diff",
509
+ SAVE_BTN: "\u{1F4BE} Enregistrer",
510
+ CLOSE_BTN: "\u2715 Fermer",
511
+ BACK_TO_EDITOR: "\u2190 Retour \xE0 l'\xE9diteur"
512
+ },
513
+ RECORDER: {
514
+ FS_TITLE: "\u{1F4C1} Acc\xE8s aux fichiers",
515
+ FS_LATER_BTN: "Pas maintenant",
516
+ FS_SELECT_BTN: "\u{1F4C1} S\xE9lectionner un dossier",
517
+ FS_SUCCESS_TOAST: "\u2713 Dossier Cypress configur\xE9",
518
+ FS_ERROR_TOAST: "Erreur lors de l'acc\xE8s au dossier",
519
+ FS_INTRO_HTML: `L'<strong style="color:#e6edf3">\xE9diteur avanc\xE9</strong> peut ins\xE9rer vos tests directement dans les fichiers <code style="background:#0d1117;padding:1px 6px;border-radius:4px;font-size:11px;color:#c9d1d9">.cy.ts</code> de Cypress.`,
520
+ FS_STRUCTURE_HINT_HTML: `S\xE9lectionnez le dossier qui contient le sous-dossier <code style="background:#0d1117;padding:1px 5px;border-radius:4px;font-size:10px;color:#c9d1d9">e2e/</code>. L'extension attend cette structure :`,
521
+ FS_TREE_PICK_HINT: "\u2190 s\xE9lectionnez ce dossier",
522
+ FS_TREE_READ_HINT: "\u2190 l'extension lira ici",
523
+ FS_PERMISSION_NOTE: "L'autorisation est enregistr\xE9e dans le navigateur et ne sera plus demand\xE9e.",
524
+ ASSERTION_SECTION: "\u2795 Ajouter une assertion",
525
+ ASSERT_SELECTOR_PH: 's\xE9lecteur, ex : [data-cy="btn"]',
526
+ ASSERT_VALUE_PH: "valeur attendue",
527
+ ASSERT_ADD_BTN: "\u2795 Ajouter",
528
+ FILE_EDITOR_TITLE: "\u270F\uFE0F \xC9diteur de fichier",
529
+ FILE_SAVED_TOAST: "\u2713 Fichier enregistr\xE9",
530
+ FILE_SAVE_ERROR_TOAST: "Erreur lors de l'enregistrement",
531
+ BTN_CONFIG: "Config",
532
+ BTN_FILES: "Fichiers",
533
+ BTN_COMMANDS: "Commandes",
534
+ BTN_TESTS: "Tests",
535
+ PAUSE_TITLE: "Pause (Ctrl+P)",
536
+ RESUME_TITLE: "Reprendre (Ctrl+P)",
537
+ BADGE_PAUSED: "\u23F8 PAUSE",
538
+ BADGE_REC: "\u25CF REC",
539
+ STOP_TITLE: "Arr\xEAter (Ctrl+R)",
540
+ START_TITLE: "Enregistrer (Ctrl+R)"
541
+ }
542
+ };
543
+
544
+ // src/i18n/it.ts
545
+ var I18N_IT = {
546
+ MAIN_FRAME: {
547
+ SETTINGS: "Impostazioni",
548
+ SHOW_ADVANCED_EDITOR: "Editor avanzato",
549
+ DIALOG_SAVED_TESTS: "Test salvati",
550
+ DIALOG_COMMANDS: "Anteprima comandi Cypress",
551
+ DIALOG_SAVE: "Salvare la registrazione?"
552
+ },
553
+ SAVE_TEST: {
554
+ DESC_LABEL: "Descrizione del test",
555
+ CANCEL: "Annulla",
556
+ ASK: "Salvare il test registrato?",
557
+ YES_CONTINUE: "S\xEC, continua",
558
+ NO_DISCARD: "No, annulla",
559
+ DESC_PLACEHOLDER: "Es: Login riuscito con credenziali valide",
560
+ TAGS_LABEL: "Etichette (opzionale, per filtrare dopo):",
561
+ TAGS_PLACEHOLDER: "smoke, login, regressione\u2026",
562
+ ADD_TAG: "+ Aggiungi",
563
+ NO_TAGS: "Nessuna etichetta",
564
+ SAVE_BTN: "\u{1F4BE} Salva",
565
+ SAVE_AND_EDIT: "\u{1F4DD} Salva e modifica",
566
+ REMOVE_TAG_TITLE: "Rimuovi"
567
+ },
568
+ TEST_EDITOR: {
569
+ NO_TAGS: "Nessuna etichetta",
570
+ SELECTED_SINGULAR: "selezionato",
571
+ SELECTED_PLURAL: "selezionati",
572
+ DESCRIBE_PLACEHOLDER: "Nome del describe (opzionale)",
573
+ COPY_DESCRIBE: "\u{1F5C2} Copia describe()",
574
+ MULTI_SELECT: "\u{1F5C2} Multi-selezione",
575
+ CANCEL_SELECT: "\u2715 Annulla",
576
+ SECTION_COMMANDS: "Comandi",
577
+ SECTION_INTERCEPTORS: "Interceptor",
578
+ COPY_CMDS_BTN: "\u{1F4CB} Copia comandi",
579
+ COPY_ICPS_BTN: "\u{1F4CB} Copia interceptor",
580
+ DEFAULT_DESCRIBE: "Suite di test",
581
+ NO_TESTS: "Nessun test salvato",
582
+ DELETE_TITLE: "Elimina"
583
+ },
584
+ CONFIG: {
585
+ LANG_SECTION: "\u{1F310} Lingua",
586
+ LANG_FIELD: "Interfaccia",
587
+ HTTP_SECTION: "\u26A1 HTTP Avanzato",
588
+ HTTP_TITLE: "Validazioni del body",
589
+ HTTP_SUB: "GET \u2192 risposta \xB7 POST/PUT \u2192 richiesta",
590
+ SELECTOR_SECTION: "\u{1F3AF} Strategia selettore",
591
+ SELECTOR_LABEL: "Attributo preferito per cy.get()",
592
+ SELECTOR_OPT_DATACY: "data-cy (predefinito)",
593
+ SELECTOR_OPT_TESTID: "data-testid",
594
+ SELECTOR_OPT_ARIA: "aria-label",
595
+ SELECTOR_OPT_ID: "id (#elemento)",
596
+ SELECTOR_HINT: "Il registratore cercher\xE0 prima questo attributo. Se assente, prover\xE0 gli altri.",
597
+ FOLDER_SECTION: "\u{1F4C1} Cartella Cypress",
598
+ FOLDER_CONNECTED: "connesso \u2014",
599
+ FOLDER_NOT_SET: "non configurato",
600
+ FOLDER_CHANGE_BTN: "\u{1F4C1} Cambia cartella",
601
+ FOLDER_SELECT_BTN: "\u{1F4C1} Seleziona cartella",
602
+ FOLDER_REVOKE_BTN: "\u2715 Revoca accesso",
603
+ DATA_SECTION: "\u{1F4BE} Dati",
604
+ DATA_DESC: "Esporta tutti i tuoi test in JSON o importa un backup.",
605
+ EXPORT_BTN: "\u2B06\uFE0F Esporta test",
606
+ IMPORT_BTN: "\u2B07\uFE0F Importa test",
607
+ FOLDER_UPDATED_TOAST: "\u2713 Cartella Cypress aggiornata",
608
+ FOLDER_ERROR_TOAST: "Errore durante l'accesso alla cartella",
609
+ IMPORT_SUCCESS: "Dati importati correttamente.",
610
+ IMPORT_ERROR: "Errore durante l'importazione.",
611
+ JSON_INVALID: "Il file non \xE8 un JSON valido.",
612
+ JSON_BAD_FORMAT: "Il file non ha il formato previsto.",
613
+ SMART_SELECTOR_SECTION: "\u{1F50D} Selettore intelligente",
614
+ SMART_SELECTOR_TITLE: "Selettore intelligente",
615
+ SMART_SELECTOR_SUB: "Mostra un selettore quando si fa clic su elementi senza selettore valido."
616
+ },
617
+ SELECTOR_PICKER: {
618
+ TITLE: "Seleziona un elemento DOM",
619
+ PREVIEW_LABEL: "navigare",
620
+ CONFIRM_HINT: "confermare",
621
+ CANCEL_HINT: "annullare",
622
+ QUALITY_EXCELLENT: "Eccellente \u2014 attributo di test presente (data-cy / data-testid / aria-label)",
623
+ QUALITY_GOOD: "Buono \u2014 id valido",
624
+ QUALITY_ACCEPTABLE: "Accettabile \u2014 solo classi CSS",
625
+ QUALITY_POOR: "Sconsigliato \u2014 solo tag o stile inline"
626
+ },
627
+ TEST_PREVISUALIZER: {
628
+ INTERCEPTORS: "Interceptor",
629
+ COPY_CMDS_BTN: "\u{1F4CB} Copia comandi",
630
+ SHOW_INTERCEPTORS: "\u25BC Vedi interceptor",
631
+ HIDE_INTERCEPTORS: "\u25B2 Nascondi interceptor",
632
+ SECTION_COMMANDS: "Comandi",
633
+ NO_CMDS_YET: "Ancora nessun comando",
634
+ NO_ICP_SHORT: "Nessun interceptor",
635
+ COPY_ICP_BTN: "\u{1F4CB} Copia interceptor",
636
+ UP_TITLE: "Su",
637
+ DOWN_TITLE: "Gi\xF9",
638
+ DEL_TITLE: "Elimina"
639
+ },
640
+ ADVANCED_EDITOR: {
641
+ REAUTH_BTN: "\u{1F513} Riattiva accesso",
642
+ PERMISSION_EXPIRED: "\u{1F511} Autorizzazione scaduta",
643
+ PERMISSION_EXPIRED_HINT: "Clicca per riattivare l'accesso alla cartella Cypress.",
644
+ NO_ACCESS: "\u{1F512} Nessun accesso ai file locali",
645
+ NO_ACCESS_HINT: "Configura l'accesso in \u2699\uFE0F Config.",
646
+ NO_FILES: "Nessun file",
647
+ SELECT_FILE: "\u2190 Seleziona un file dall'albero",
648
+ IT_LABEL: "\u{1F9EA} it() da inserire:",
649
+ COPY_IT_BTN: "\u{1F4CB} Copia it()",
650
+ BEFORE_EACH_LABEL: "\u{1F500} beforeEach() interceptor:",
651
+ COPY_ICP_BTN: "\u{1F4CB} Copia interceptor",
652
+ INSERT_BTN: "\u{1F4BE} Inserisci nel file",
653
+ EDIT_MANUAL_BTN: "\u270F\uFE0F Modifica manualmente",
654
+ CLOSE_BTN: "\u2715 Chiudi"
655
+ },
656
+ FILE_PREVIEW: {
657
+ NO_FILE: "Nessun file",
658
+ COPY_TITLE: "Copia il contenuto dell'editor",
659
+ BLOCK_IT: "\u{1F9EA} it()",
660
+ COPY_BTN: "\u{1F4CB} Copia",
661
+ BLOCK_BEFORE_EACH: "\u{1F500} beforeEach()",
662
+ NO_CHANGES: "Nessuna modifica rispetto all'originale",
663
+ NO_CHANGES_SHORT: "Nessuna modifica",
664
+ LAUNCH_BTN: "\u25B6 Esegui test",
665
+ DIFF_BTN: "\u{1F4CA} Diff",
666
+ SAVE_BTN: "\u{1F4BE} Salva",
667
+ CLOSE_BTN: "\u2715 Chiudi",
668
+ BACK_TO_EDITOR: "\u2190 Torna all'editor"
669
+ },
670
+ RECORDER: {
671
+ FS_TITLE: "\u{1F4C1} Accesso ai file",
672
+ FS_LATER_BTN: "Non ora",
673
+ FS_SELECT_BTN: "\u{1F4C1} Seleziona cartella",
674
+ FS_SUCCESS_TOAST: "\u2713 Cartella Cypress configurata",
675
+ FS_ERROR_TOAST: "Errore durante l'accesso alla cartella",
676
+ FS_INTRO_HTML: `L'<strong style="color:#e6edf3">editor avanzato</strong> pu\xF2 inserire i tuoi test direttamente nei file <code style="background:#0d1117;padding:1px 6px;border-radius:4px;font-size:11px;color:#c9d1d9">.cy.ts</code> di Cypress.`,
677
+ FS_STRUCTURE_HINT_HTML: `Seleziona la cartella che contiene la sottocartella <code style="background:#0d1117;padding:1px 5px;border-radius:4px;font-size:10px;color:#c9d1d9">e2e/</code>. L'estensione si aspetta questa struttura:`,
678
+ FS_TREE_PICK_HINT: "\u2190 seleziona questa cartella",
679
+ FS_TREE_READ_HINT: "\u2190 l'estensione legger\xE0 qui",
680
+ FS_PERMISSION_NOTE: "Il permesso viene salvato nel browser e non sar\xE0 richiesto di nuovo.",
681
+ ASSERTION_SECTION: "\u2795 Aggiungi asserzione",
682
+ ASSERT_SELECTOR_PH: 'selettore, es: [data-cy="btn"]',
683
+ ASSERT_VALUE_PH: "valore atteso",
684
+ ASSERT_ADD_BTN: "\u2795 Aggiungi",
685
+ FILE_EDITOR_TITLE: "\u270F\uFE0F Editor di file",
686
+ FILE_SAVED_TOAST: "\u2713 File salvato",
687
+ FILE_SAVE_ERROR_TOAST: "Errore durante il salvataggio",
688
+ BTN_CONFIG: "Config",
689
+ BTN_FILES: "File",
690
+ BTN_COMMANDS: "Comandi",
691
+ BTN_TESTS: "Test",
692
+ PAUSE_TITLE: "Pausa (Ctrl+P)",
693
+ RESUME_TITLE: "Riprendi (Ctrl+P)",
694
+ BADGE_PAUSED: "\u23F8 PAUSA",
695
+ BADGE_REC: "\u25CF REC",
696
+ STOP_TITLE: "Ferma (Ctrl+R)",
697
+ START_TITLE: "Registra (Ctrl+R)"
698
+ }
699
+ };
700
+
701
+ // src/i18n/de.ts
702
+ var I18N_DE = {
703
+ MAIN_FRAME: {
704
+ SETTINGS: "Einstellungen",
705
+ SHOW_ADVANCED_EDITOR: "Erweiterter Editor",
706
+ DIALOG_SAVED_TESTS: "Gespeicherte Tests",
707
+ DIALOG_COMMANDS: "Cypress-Befehle Vorschau",
708
+ DIALOG_SAVE: "Aufzeichnung speichern?"
709
+ },
710
+ SAVE_TEST: {
711
+ DESC_LABEL: "Testbeschreibung",
712
+ CANCEL: "Abbrechen",
713
+ ASK: "Aufgezeichneten Test speichern?",
714
+ YES_CONTINUE: "Ja, weiter",
715
+ NO_DISCARD: "Nein, verwerfen",
716
+ DESC_PLACEHOLDER: "Z. B.: Erfolgreiche Anmeldung mit g\xFCltigen Zugangsdaten",
717
+ TAGS_LABEL: "Tags (optional, zum sp\xE4teren Filtern):",
718
+ TAGS_PLACEHOLDER: "smoke, login, regression\u2026",
719
+ ADD_TAG: "+ Hinzuf\xFCgen",
720
+ NO_TAGS: "Keine Tags",
721
+ SAVE_BTN: "\u{1F4BE} Speichern",
722
+ SAVE_AND_EDIT: "\u{1F4DD} Speichern und bearbeiten",
723
+ REMOVE_TAG_TITLE: "Entfernen"
724
+ },
725
+ TEST_EDITOR: {
726
+ NO_TAGS: "Keine Tags",
727
+ SELECTED_SINGULAR: "ausgew\xE4hlt",
728
+ SELECTED_PLURAL: "ausgew\xE4hlt",
729
+ DESCRIBE_PLACEHOLDER: "describe-Name (optional)",
730
+ COPY_DESCRIBE: "\u{1F5C2} describe() kopieren",
731
+ MULTI_SELECT: "\u{1F5C2} Mehrfachauswahl",
732
+ CANCEL_SELECT: "\u2715 Abbrechen",
733
+ SECTION_COMMANDS: "Befehle",
734
+ SECTION_INTERCEPTORS: "Interceptors",
735
+ COPY_CMDS_BTN: "\u{1F4CB} Befehle kopieren",
736
+ COPY_ICPS_BTN: "\u{1F4CB} Interceptors kopieren",
737
+ DEFAULT_DESCRIBE: "Test-Suite",
738
+ NO_TESTS: "Keine gespeicherten Tests",
739
+ DELETE_TITLE: "L\xF6schen"
740
+ },
741
+ CONFIG: {
742
+ LANG_SECTION: "\u{1F310} Sprache",
743
+ LANG_FIELD: "Oberfl\xE4che",
744
+ HTTP_SECTION: "\u26A1 Erweitertes HTTP",
745
+ HTTP_TITLE: "Body-Validierungen",
746
+ HTTP_SUB: "GET \u2192 Antwort \xB7 POST/PUT \u2192 Anfrage",
747
+ SELECTOR_SECTION: "\u{1F3AF} Selector-Strategie",
748
+ SELECTOR_LABEL: "Bevorzugtes Attribut f\xFCr cy.get()",
749
+ SELECTOR_OPT_DATACY: "data-cy (Standard)",
750
+ SELECTOR_OPT_TESTID: "data-testid",
751
+ SELECTOR_OPT_ARIA: "aria-label",
752
+ SELECTOR_OPT_ID: "id (#element)",
753
+ SELECTOR_HINT: "Der Recorder sucht zuerst nach diesem Attribut. Falls nicht gefunden, versucht er die anderen.",
754
+ FOLDER_SECTION: "\u{1F4C1} Cypress-Ordner",
755
+ FOLDER_CONNECTED: "verbunden \u2014",
756
+ FOLDER_NOT_SET: "nicht konfiguriert",
757
+ FOLDER_CHANGE_BTN: "\u{1F4C1} Ordner wechseln",
758
+ FOLDER_SELECT_BTN: "\u{1F4C1} Ordner ausw\xE4hlen",
759
+ FOLDER_REVOKE_BTN: "\u2715 Zugriff widerrufen",
760
+ DATA_SECTION: "\u{1F4BE} Daten",
761
+ DATA_DESC: "Exportieren Sie alle Tests als JSON oder importieren Sie ein Backup.",
762
+ EXPORT_BTN: "\u2B06\uFE0F Tests exportieren",
763
+ IMPORT_BTN: "\u2B07\uFE0F Tests importieren",
764
+ FOLDER_UPDATED_TOAST: "\u2713 Cypress-Ordner aktualisiert",
765
+ FOLDER_ERROR_TOAST: "Fehler beim Zugriff auf den Ordner",
766
+ IMPORT_SUCCESS: "Daten erfolgreich importiert.",
767
+ IMPORT_ERROR: "Fehler beim Importieren.",
768
+ JSON_INVALID: "Die Datei ist kein g\xFCltiges JSON.",
769
+ JSON_BAD_FORMAT: "Die Datei hat nicht das erwartete Format.",
770
+ SMART_SELECTOR_SECTION: "\u{1F50D} Intelligenter Selektor",
771
+ SMART_SELECTOR_TITLE: "Intelligenter Selektor",
772
+ SMART_SELECTOR_SUB: "Zeigt ein Auswahlfeld, wenn auf Elemente ohne g\xFCltigen Selektor geklickt wird."
773
+ },
774
+ SELECTOR_PICKER: {
775
+ TITLE: "DOM-Element ausw\xE4hlen",
776
+ PREVIEW_LABEL: "navigieren",
777
+ CONFIRM_HINT: "best\xE4tigen",
778
+ CANCEL_HINT: "abbrechen",
779
+ QUALITY_EXCELLENT: "Ausgezeichnet \u2014 Test-Attribut vorhanden (data-cy / data-testid / aria-label)",
780
+ QUALITY_GOOD: "Gut \u2014 g\xFCltige ID",
781
+ QUALITY_ACCEPTABLE: "Akzeptabel \u2014 nur CSS-Klassen",
782
+ QUALITY_POOR: "Nicht empfohlen \u2014 nur Tag oder Inline-Style"
783
+ },
784
+ TEST_PREVISUALIZER: {
785
+ INTERCEPTORS: "Interceptors",
786
+ COPY_CMDS_BTN: "\u{1F4CB} Befehle kopieren",
787
+ SHOW_INTERCEPTORS: "\u25BC Interceptors anzeigen",
788
+ HIDE_INTERCEPTORS: "\u25B2 Interceptors ausblenden",
789
+ SECTION_COMMANDS: "Befehle",
790
+ NO_CMDS_YET: "Noch keine Befehle",
791
+ NO_ICP_SHORT: "Keine Interceptors",
792
+ COPY_ICP_BTN: "\u{1F4CB} Interceptors kopieren",
793
+ UP_TITLE: "Nach oben",
794
+ DOWN_TITLE: "Nach unten",
795
+ DEL_TITLE: "L\xF6schen"
796
+ },
797
+ ADVANCED_EDITOR: {
798
+ REAUTH_BTN: "\u{1F513} Zugriff reaktivieren",
799
+ PERMISSION_EXPIRED: "\u{1F511} Berechtigung abgelaufen",
800
+ PERMISSION_EXPIRED_HINT: "Klicken Sie, um den Zugriff auf Ihren Cypress-Ordner zu reaktivieren.",
801
+ NO_ACCESS: "\u{1F512} Kein Zugriff auf lokale Dateien",
802
+ NO_ACCESS_HINT: "Konfigurieren Sie den Zugriff unter \u2699\uFE0F Config.",
803
+ NO_FILES: "Keine Dateien",
804
+ SELECT_FILE: "\u2190 W\xE4hlen Sie eine Datei aus dem Baum",
805
+ IT_LABEL: "\u{1F9EA} it() zum Einf\xFCgen:",
806
+ COPY_IT_BTN: "\u{1F4CB} it() kopieren",
807
+ BEFORE_EACH_LABEL: "\u{1F500} beforeEach() Interceptors:",
808
+ COPY_ICP_BTN: "\u{1F4CB} Interceptors kopieren",
809
+ INSERT_BTN: "\u{1F4BE} In Datei einf\xFCgen",
810
+ EDIT_MANUAL_BTN: "\u270F\uFE0F Manuell bearbeiten",
811
+ CLOSE_BTN: "\u2715 Schlie\xDFen"
812
+ },
813
+ FILE_PREVIEW: {
814
+ NO_FILE: "Keine Datei",
815
+ COPY_TITLE: "Editor-Inhalt kopieren",
816
+ BLOCK_IT: "\u{1F9EA} it()",
817
+ COPY_BTN: "\u{1F4CB} Kopieren",
818
+ BLOCK_BEFORE_EACH: "\u{1F500} beforeEach()",
819
+ NO_CHANGES: "Keine \xC4nderungen gegen\xFCber dem Original",
820
+ NO_CHANGES_SHORT: "Keine \xC4nderungen",
821
+ LAUNCH_BTN: "\u25B6 Test starten",
822
+ DIFF_BTN: "\u{1F4CA} Diff",
823
+ SAVE_BTN: "\u{1F4BE} Speichern",
824
+ CLOSE_BTN: "\u2715 Schlie\xDFen",
825
+ BACK_TO_EDITOR: "\u2190 Zur\xFCck zum Editor"
826
+ },
827
+ RECORDER: {
828
+ FS_TITLE: "\u{1F4C1} Dateizugriff",
829
+ FS_LATER_BTN: "Nicht jetzt",
830
+ FS_SELECT_BTN: "\u{1F4C1} Ordner ausw\xE4hlen",
831
+ FS_SUCCESS_TOAST: "\u2713 Cypress-Ordner konfiguriert",
832
+ FS_ERROR_TOAST: "Fehler beim Zugriff auf den Ordner",
833
+ FS_INTRO_HTML: 'Der <strong style="color:#e6edf3">erweiterte Editor</strong> kann Ihre Tests direkt in die Cypress-Dateien <code style="background:#0d1117;padding:1px 6px;border-radius:4px;font-size:11px;color:#c9d1d9">.cy.ts</code> einf\xFCgen.',
834
+ FS_STRUCTURE_HINT_HTML: 'W\xE4hlen Sie den Ordner, der den Unterordner <code style="background:#0d1117;padding:1px 5px;border-radius:4px;font-size:10px;color:#c9d1d9">e2e/</code> enth\xE4lt. Die Erweiterung erwartet diese Struktur:',
835
+ FS_TREE_PICK_HINT: "\u2190 diesen Ordner ausw\xE4hlen",
836
+ FS_TREE_READ_HINT: "\u2190 die Erweiterung liest hier",
837
+ FS_PERMISSION_NOTE: "Die Berechtigung wird im Browser gespeichert und nicht erneut angefordert.",
838
+ ASSERTION_SECTION: "\u2795 Assertion hinzuf\xFCgen",
839
+ ASSERT_SELECTOR_PH: 'Selektor, z. B.: [data-cy="btn"]',
840
+ ASSERT_VALUE_PH: "erwarteter Wert",
841
+ ASSERT_ADD_BTN: "\u2795 Hinzuf\xFCgen",
842
+ FILE_EDITOR_TITLE: "\u270F\uFE0F Datei-Editor",
843
+ FILE_SAVED_TOAST: "\u2713 Datei gespeichert",
844
+ FILE_SAVE_ERROR_TOAST: "Fehler beim Speichern",
845
+ BTN_CONFIG: "Config",
846
+ BTN_FILES: "Dateien",
847
+ BTN_COMMANDS: "Befehle",
848
+ BTN_TESTS: "Tests",
849
+ PAUSE_TITLE: "Pausieren (Ctrl+P)",
850
+ RESUME_TITLE: "Fortsetzen (Ctrl+P)",
851
+ BADGE_PAUSED: "\u23F8 PAUSE",
852
+ BADGE_REC: "\u25CF REC",
853
+ STOP_TITLE: "Stopp (Ctrl+R)",
854
+ START_TITLE: "Aufzeichnen (Ctrl+R)"
855
+ }
856
+ };
857
+
858
+ // src/services/translation.service.ts
859
+ var TranslationService = class {
860
+ lang;
861
+ translations = {
862
+ es: I18N_ES,
863
+ en: I18N_EN,
864
+ fr: I18N_FR,
865
+ it: I18N_IT,
866
+ de: I18N_DE
867
+ };
868
+ constructor() {
869
+ this.lang = this.detectLang();
870
+ }
871
+ setLang(lang) {
872
+ this.lang = lang;
873
+ }
874
+ getLang() {
875
+ return this.lang;
876
+ }
877
+ translate(key) {
878
+ const keys = key.split(".");
879
+ let value = this.translations[this.lang];
880
+ for (const k of keys) {
881
+ value = value?.[k];
882
+ if (value === void 0) return key;
883
+ }
884
+ return value;
885
+ }
886
+ detectLang() {
887
+ const browserLang = navigator.language.split("-")[0];
888
+ return isLang(browserLang) ? browserLang : "es";
889
+ }
890
+ };
891
+ var translationService = new TranslationService();
892
+
893
+ // src/services/transformation.service.ts
894
+ var TransformationService = class {
895
+ toLang(lang) {
896
+ return isLang(lang) ? lang : "en";
897
+ }
898
+ generateItDescription(description, commands) {
899
+ const commandsBlock = commands.map((cmd) => ` ${cmd}`).join("\n");
900
+ return `it('${description}', () => {
901
+ ${commandsBlock}
902
+ });`;
903
+ }
904
+ };
905
+ var transformationService = new TransformationService();
906
+
907
+ // src/services/advanced-test.transformation.service.ts
908
+ var AdvancedTestTransformationService = class {
909
+ insertBeforeEach(content, interceptors, alertFn) {
910
+ const match = content.match(/(describe\s*\(.*?{)/s);
911
+ if (!match) {
912
+ alertFn?.("ADVANCED_EDITOR.NO_DESCRIBE");
913
+ return "";
914
+ }
915
+ const insertPos = (match.index ?? 0) + match[0].length;
916
+ const beforeEachBlock = `
917
+ beforeEach(() => {
918
+ ${interceptors} })
919
+ `;
920
+ return content.slice(0, insertPos) + beforeEachBlock + content.slice(insertPos);
921
+ }
922
+ insertItBlock(content, itBlock, alertFn) {
923
+ const idx = content.lastIndexOf("})");
924
+ if (idx === -1) {
925
+ alertFn?.("ADVANCED_EDITOR.NO_END");
926
+ return "";
927
+ }
928
+ return content.slice(0, idx) + "\n" + itBlock + "\n" + content.slice(idx);
929
+ }
930
+ isFile(file) {
931
+ return !!file && file.kind === "file";
932
+ }
933
+ async scanDirectory(dirHandle) {
934
+ const result = { name: dirHandle.name, kind: "directory", children: [] };
935
+ for await (const entry of dirHandle.values()) {
936
+ if (entry.kind === "directory") {
937
+ result.children.push(await this.scanDirectory(entry));
938
+ } else {
939
+ result.children.push({ name: entry.name, kind: "file" });
940
+ }
941
+ }
942
+ return result;
943
+ }
944
+ };
945
+ var advancedTestTransformationService = new AdvancedTestTransformationService();
946
+
947
+ // src/utils/subject.ts
948
+ var Subject = class {
949
+ _value;
950
+ listeners = /* @__PURE__ */ new Set();
951
+ constructor(initialValue) {
952
+ this._value = initialValue;
953
+ }
954
+ next(value) {
955
+ this._value = value;
956
+ this.listeners.forEach((l) => l(value));
957
+ }
958
+ getValue() {
959
+ return this._value;
960
+ }
961
+ subscribe(fn) {
962
+ this.listeners.add(fn);
963
+ return () => this.listeners.delete(fn);
964
+ }
965
+ };
966
+
967
+ // src/services/recording.service.ts
968
+ var OWN_SELECTOR = '[data-cy="lib-e2e-cypress-for-dummys"]';
969
+ var RecordingService = class {
970
+ commands$ = new Subject([]);
971
+ interceptors$ = new Subject([]);
972
+ isRecording$ = new Subject(false);
973
+ isPaused$ = new Subject(false);
974
+ selectorNotFound$ = new Subject(null);
975
+ inputDebounceTimers = /* @__PURE__ */ new Map();
976
+ abort = new AbortController();
977
+ selectorStrategy = "data-cy";
978
+ // Stored originals for history patching cleanup
979
+ origPushState = history.pushState.bind(history);
980
+ origReplaceState = history.replaceState.bind(history);
981
+ constructor() {
982
+ this.listenToClicks();
983
+ this.listenToInput();
984
+ this.listenToSelect();
985
+ this.listenToRouteChanges();
986
+ }
987
+ // ── Public API ────────────────────────────────────────────────────────────
988
+ startRecording() {
989
+ this.isPaused$.next(false);
990
+ this.isRecording$.next(true);
991
+ this.addCommand(`cy.viewport(1900, 1200)`);
992
+ this.addCommand(`cy.visit('${window.location.pathname}')`);
993
+ this.addCommand(`cy.get('[data-cy="lib-e2e-cypress-for-dummys"]').invoke('hide');`);
994
+ }
995
+ stopRecording() {
996
+ this.isPaused$.next(false);
997
+ this.isRecording$.next(false);
998
+ }
999
+ toggleRecording() {
1000
+ if (this.isRecording$.getValue()) {
1001
+ this.stopRecording();
1002
+ } else {
1003
+ this.startRecording();
1004
+ }
1005
+ }
1006
+ pauseRecording() {
1007
+ if (!this.isRecording$.getValue()) return;
1008
+ this.isPaused$.next(true);
1009
+ }
1010
+ resumeRecording() {
1011
+ this.isPaused$.next(false);
1012
+ }
1013
+ togglePause() {
1014
+ if (this.isPaused$.getValue()) {
1015
+ this.resumeRecording();
1016
+ } else {
1017
+ this.pauseRecording();
1018
+ }
1019
+ }
1020
+ addCommand(cmd) {
1021
+ if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
1022
+ this.commands$.next([...this.commands$.getValue(), cmd]);
1023
+ }
1024
+ /** Appends a command regardless of recording/paused state (e.g. assertions added manually). */
1025
+ appendCommand(cmd) {
1026
+ this.commands$.next([...this.commands$.getValue(), cmd]);
1027
+ }
1028
+ removeCommand(index) {
1029
+ const cmds = this.commands$.getValue();
1030
+ if (index < 0 || index >= cmds.length) return;
1031
+ this.commands$.next([...cmds.slice(0, index), ...cmds.slice(index + 1)]);
1032
+ }
1033
+ moveCommand(from, to) {
1034
+ const cmds = [...this.commands$.getValue()];
1035
+ if (from < 0 || to < 0 || from >= cmds.length || to >= cmds.length || from === to) return;
1036
+ const [item] = cmds.splice(from, 1);
1037
+ cmds.splice(to, 0, item);
1038
+ this.commands$.next(cmds);
1039
+ }
1040
+ removeInterceptor(index) {
1041
+ const ints = this.interceptors$.getValue();
1042
+ if (index < 0 || index >= ints.length) return;
1043
+ this.interceptors$.next([...ints.slice(0, index), ...ints.slice(index + 1)]);
1044
+ }
1045
+ registerInterceptor(method, url, alias) {
1046
+ if (this.isPaused$.getValue()) return;
1047
+ const command = `cy.intercept('${method}', '${this.urlToWildcard(url, method)}').as('${alias}')`;
1048
+ const current = this.interceptors$.getValue();
1049
+ if (!current.includes(command)) {
1050
+ this.interceptors$.next([...current, command]);
1051
+ }
1052
+ }
1053
+ clearCommands() {
1054
+ this.commands$.next([]);
1055
+ this.interceptors$.next([]);
1056
+ }
1057
+ clearInterceptors() {
1058
+ this.interceptors$.next([]);
1059
+ }
1060
+ getCommandsSnapshot() {
1061
+ return this.commands$.getValue();
1062
+ }
1063
+ getInterceptorsSnapshot() {
1064
+ return this.interceptors$.getValue();
1065
+ }
1066
+ getPausedSnapshot() {
1067
+ return this.isPaused$.getValue();
1068
+ }
1069
+ onCommandsChange(fn) {
1070
+ return this.commands$.subscribe(fn);
1071
+ }
1072
+ onInterceptorsChange(fn) {
1073
+ return this.interceptors$.subscribe(fn);
1074
+ }
1075
+ onRecordingChange(fn) {
1076
+ return this.isRecording$.subscribe(fn);
1077
+ }
1078
+ onPauseChange(fn) {
1079
+ return this.isPaused$.subscribe(fn);
1080
+ }
1081
+ onSelectorNotFound(fn) {
1082
+ return this.selectorNotFound$.subscribe((v) => {
1083
+ if (v) fn(v.target, v.action);
1084
+ });
1085
+ }
1086
+ destroy() {
1087
+ this.abort.abort();
1088
+ history.pushState = this.origPushState;
1089
+ history.replaceState = this.origReplaceState;
1090
+ this.inputDebounceTimers.forEach((t) => clearTimeout(t));
1091
+ this.inputDebounceTimers.clear();
1092
+ }
1093
+ // ── DOM listeners ─────────────────────────────────────────────────────────
1094
+ listenToClicks() {
1095
+ document.addEventListener(
1096
+ "click",
1097
+ (e) => {
1098
+ if (!this.isRecording$.getValue()) return;
1099
+ const target = e.target;
1100
+ if (!target || this.isOwnElement(target)) return;
1101
+ const tag = target.tagName?.toLowerCase();
1102
+ if (tag === "body" || tag === "html") return;
1103
+ this.handleClickEvent(target);
1104
+ },
1105
+ { signal: this.abort.signal }
1106
+ );
1107
+ }
1108
+ listenToInput() {
1109
+ document.addEventListener(
1110
+ "input",
1111
+ (e) => {
1112
+ if (!this.isRecording$.getValue()) return;
1113
+ const target = e.target;
1114
+ if (!target || this.isOwnElement(target)) return;
1115
+ this.handleInputEvent(target);
1116
+ },
1117
+ { signal: this.abort.signal }
1118
+ );
1119
+ }
1120
+ listenToSelect() {
1121
+ document.addEventListener(
1122
+ "change",
1123
+ (e) => {
1124
+ if (!this.isRecording$.getValue()) return;
1125
+ const target = e.target;
1126
+ if (!target || this.isOwnElement(target)) return;
1127
+ if (target.tagName.toLowerCase() === "select") {
1128
+ this.handleSelectEvent(target);
1129
+ }
1130
+ },
1131
+ { signal: this.abort.signal }
1132
+ );
1133
+ }
1134
+ listenToRouteChanges() {
1135
+ let lastUrl = window.location.pathname + window.location.search + window.location.hash;
1136
+ const addUrlCommand = (newUrl) => {
1137
+ if (!this.isRecording$.getValue()) return;
1138
+ if (newUrl === lastUrl) return;
1139
+ this.addCommand(`cy.url().should('include', '${newUrl}')`);
1140
+ lastUrl = newUrl;
1141
+ };
1142
+ const wrapMethod = (type) => {
1143
+ const orig = history[type].bind(history);
1144
+ history[type] = (data, unused, url) => {
1145
+ const result = orig(data, unused, url);
1146
+ let newUrl = window.location.pathname + window.location.search + window.location.hash;
1147
+ if (typeof url === "string" && url.length > 0) {
1148
+ const a = document.createElement("a");
1149
+ a.href = url;
1150
+ newUrl = a.pathname + a.search + a.hash;
1151
+ } else if (url instanceof URL) {
1152
+ newUrl = url.pathname + url.search + url.hash;
1153
+ }
1154
+ addUrlCommand(newUrl);
1155
+ return result;
1156
+ };
1157
+ };
1158
+ wrapMethod("pushState");
1159
+ wrapMethod("replaceState");
1160
+ window.addEventListener(
1161
+ "popstate",
1162
+ () => {
1163
+ const newUrl = window.location.pathname + window.location.search + window.location.hash;
1164
+ addUrlCommand(newUrl);
1165
+ },
1166
+ { signal: this.abort.signal }
1167
+ );
1168
+ }
1169
+ // ── Click helpers ─────────────────────────────────────────────────────────
1170
+ handleClickEvent(target) {
1171
+ if (!this.isInteractiveElement(target)) {
1172
+ const tag2 = target.tagName.toLowerCase();
1173
+ if ((tag2 === "span" || tag2 === "div") && target.parentElement && (target.parentElement.tagName.toLowerCase() === "button" || target.parentElement.tagName.toLowerCase() === "mat-option") && (target.parentElement.hasAttribute("data-cy") || target.parentElement.hasAttribute("id"))) {
1174
+ target = target.parentElement;
1175
+ }
1176
+ const matSelect = target.closest("mat-select");
1177
+ if (matSelect) {
1178
+ const sel = this.getReliableSelector(matSelect);
1179
+ if (sel) {
1180
+ this.addCommand(`cy.get('${sel}').click()`);
1181
+ } else {
1182
+ this.selectorNotFound$.next({ target: matSelect, action: "click" });
1183
+ }
1184
+ return;
1185
+ }
1186
+ }
1187
+ const tag = target.tagName.toLowerCase();
1188
+ if (tag === "input" || tag === "textarea" || tag === "select") {
1189
+ const container2 = target.closest("[data-cy], [data-testid], [aria-label], [id]");
1190
+ if (!container2) this.selectorNotFound$.next({ target, action: "click" });
1191
+ return;
1192
+ }
1193
+ const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
1194
+ if (!container) {
1195
+ this.selectorNotFound$.next({ target, action: "click" });
1196
+ return;
1197
+ }
1198
+ const selector = this.getReliableSelector(container);
1199
+ if (selector === OWN_SELECTOR) return;
1200
+ if (tag === "mat-option") {
1201
+ this.handleMatOptionClick(target, selector);
1202
+ return;
1203
+ }
1204
+ this.addGenericCommand({
1205
+ selector,
1206
+ action: (s) => `cy.get('${s}').click()`
1207
+ });
1208
+ }
1209
+ handleMatOptionClick(target, selector) {
1210
+ const matSelect = target.closest("mat-select");
1211
+ let dataCy = null;
1212
+ if (matSelect) dataCy = matSelect.closest("[data-cy]")?.getAttribute("data-cy") ?? null;
1213
+ if (!dataCy) dataCy = target.closest("[data-cy]")?.getAttribute("data-cy") ?? null;
1214
+ if (dataCy) {
1215
+ this.addCommand(`cy.get('[data-cy="${dataCy}"]').click()`);
1216
+ return;
1217
+ }
1218
+ if (selector) {
1219
+ this.addCommand(`cy.get('${selector}').eq(0).click()`);
1220
+ } else {
1221
+ this.selectorNotFound$.next({ target, action: "click" });
1222
+ }
1223
+ }
1224
+ // ── Input helpers ─────────────────────────────────────────────────────────
1225
+ handleInputEvent(target) {
1226
+ const tag = target.tagName.toLowerCase();
1227
+ const isText = tag === "textarea" || tag === "input" && INPUT_TYPES.includes(target.type);
1228
+ if (!isText) return;
1229
+ const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
1230
+ if (!container) return;
1231
+ if (this.inputDebounceTimers.has(target)) {
1232
+ clearTimeout(this.inputDebounceTimers.get(target));
1233
+ }
1234
+ this.inputDebounceTimers.set(
1235
+ target,
1236
+ setTimeout(() => {
1237
+ const selector = this.getReliableSelector(container);
1238
+ const value = target.value.replace(/'/g, "\\'");
1239
+ this.addGenericCommand({
1240
+ selector,
1241
+ action: (s) => `cy.get('${s}').clear().type('${value}')`
1242
+ });
1243
+ this.inputDebounceTimers.delete(target);
1244
+ }, 1e3)
1245
+ );
1246
+ }
1247
+ // ── Select helpers ────────────────────────────────────────────────────────
1248
+ handleSelectEvent(target) {
1249
+ const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
1250
+ if (!container) return;
1251
+ const selector = this.getReliableSelector(container);
1252
+ const value = target.value.replace(/'/g, "\\'");
1253
+ this.addGenericCommand({
1254
+ selector,
1255
+ action: (s) => `cy.get('${s}').select('${value}')`
1256
+ });
1257
+ }
1258
+ // ── Shared helpers ────────────────────────────────────────────────────────
1259
+ addGenericCommand(opts) {
1260
+ if (!opts.selector || this.isOwnSelector(opts.selector)) return;
1261
+ this.addCommand(opts.action(opts.selector));
1262
+ }
1263
+ getReliableSelector(el) {
1264
+ const strategy = this.selectorStrategy;
1265
+ const dataCy = el.getAttribute("data-cy");
1266
+ const dataTestid = el.getAttribute("data-testid");
1267
+ const ariaLabel = el.getAttribute("aria-label");
1268
+ const dataDotCy = el.getAttribute("data.cy");
1269
+ const rawId = el.id;
1270
+ const validId = rawId && rawId.length < 25 && /^[a-zA-Z][\w-]*$/.test(rawId) && !FORBIDDEN_ID_PREFIXES.some((p) => rawId.startsWith(p)) && !/^\d+$/.test(rawId) ? rawId : null;
1271
+ switch (strategy) {
1272
+ case "data-testid":
1273
+ if (dataTestid) return `[data-testid="${dataTestid}"]`;
1274
+ if (dataCy) return `[data-cy="${dataCy}"]`;
1275
+ return validId ? `#${validId}` : null;
1276
+ case "aria-label":
1277
+ if (ariaLabel) return `[aria-label="${ariaLabel}"]`;
1278
+ if (dataCy) return `[data-cy="${dataCy}"]`;
1279
+ return validId ? `#${validId}` : null;
1280
+ case "id":
1281
+ if (validId) return `#${validId}`;
1282
+ if (dataCy) return `[data-cy="${dataCy}"]`;
1283
+ return dataTestid ? `[data-testid="${dataTestid}"]` : null;
1284
+ default:
1285
+ if (dataCy) return `[data-cy="${dataCy}"]`;
1286
+ if (dataDotCy) return `[data.cy="${dataDotCy}"]`;
1287
+ return validId ? `#${validId}` : null;
1288
+ }
1289
+ }
1290
+ isInteractiveElement(target) {
1291
+ if (!(target instanceof HTMLElement)) return false;
1292
+ const tag = target.tagName.toLowerCase();
1293
+ return ["select", "option", "input", "textarea", "button"].includes(tag);
1294
+ }
1295
+ isOwnSelector(selector) {
1296
+ return selector === OWN_SELECTOR;
1297
+ }
1298
+ isOwnElement(target) {
1299
+ return !!target.closest('[data-cy="lib-e2e-cypress-for-dummys"]');
1300
+ }
1301
+ urlToWildcard(url, method) {
1302
+ const u = new URL(url, "http://localhost");
1303
+ if (method.toUpperCase() === "GET" && u.search) {
1304
+ return `**${u.pathname}/**`;
1305
+ }
1306
+ return `**${u.pathname}`;
1307
+ }
1308
+ };
1309
+
1310
+ // src/services/persistence.service.ts
1311
+ import { openDB } from "idb";
1312
+ var PersistenceService = class {
1313
+ // dbName is overridable so tests can use unique names for isolation.
1314
+ constructor(dbName = DB_SCHEMA.name) {
1315
+ this.dbName = dbName;
1316
+ }
1317
+ dbName;
1318
+ _db = null;
1319
+ getDB() {
1320
+ if (!this._db) {
1321
+ this._db = openDB(this.dbName, DB_SCHEMA.version, {
1322
+ upgrade(database) {
1323
+ for (const store of DB_SCHEMA.stores) {
1324
+ if (!database.objectStoreNames.contains(store.name)) {
1325
+ const os = database.createObjectStore(store.name, {
1326
+ keyPath: store.keyPath,
1327
+ autoIncrement: store.autoIncrement
1328
+ });
1329
+ for (const index of store.indexes) {
1330
+ os.createIndex(index.name, index.keyPath, { unique: index.unique });
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+ });
1336
+ }
1337
+ return this._db;
1338
+ }
1339
+ // ── Tests ─────────────────────────────────────────────────────────────────
1340
+ async insertTest(name, commands = [], interceptors = [], tags = []) {
1341
+ const db = await this.getDB();
1342
+ const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {} };
1343
+ const id = await db.add("tests", record);
1344
+ if (commands.length) await this.insertCommands(commands, id);
1345
+ if (interceptors.length) await this.insertInterceptors(interceptors, id);
1346
+ return id;
1347
+ }
1348
+ async getAllTests() {
1349
+ const db = await this.getDB();
1350
+ const tests = await db.getAll("tests");
1351
+ return Promise.all(
1352
+ tests.map(async (test) => ({
1353
+ ...test,
1354
+ commands: await this.getCommandStrings(test.id),
1355
+ interceptors: await this.getInterceptorStrings(test.id)
1356
+ }))
1357
+ );
1358
+ }
1359
+ async getTestById(testId) {
1360
+ const db = await this.getDB();
1361
+ const record = await db.get("tests", testId);
1362
+ if (!record) return null;
1363
+ const commands = await this.getCommandStrings(testId);
1364
+ const interceptors = await this.getInterceptorStrings(testId);
1365
+ const itBlock = `it('${record.name}', () => {
1366
+ ${commands.join("\n ")}
1367
+ });`;
1368
+ const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => " " + i).join("\n") + "\n" : "";
1369
+ return { ...record, commands, interceptors, cypressCommands: commands, itBlock, interceptorsBlock };
1370
+ }
1371
+ async deleteTest(id) {
1372
+ const db = await this.getDB();
1373
+ await db.delete("tests", id);
1374
+ await this.deleteCommandsByTestId(id);
1375
+ await this.deleteInterceptorsByTestId(id);
1376
+ }
1377
+ // ── Commands ──────────────────────────────────────────────────────────────
1378
+ async insertCommands(commands, testId) {
1379
+ const db = await this.getDB();
1380
+ await Promise.all(
1381
+ commands.map((command) => db.add("commands", { command, testId, createdAt: Date.now() }))
1382
+ );
1383
+ }
1384
+ async getCommandsByTestId(testId) {
1385
+ const db = await this.getDB();
1386
+ return db.getAllFromIndex("commands", "testId", testId);
1387
+ }
1388
+ async getCommandStrings(testId) {
1389
+ return (await this.getCommandsByTestId(testId)).map((r) => r.command).filter(Boolean);
1390
+ }
1391
+ async deleteCommandsByTestId(testId) {
1392
+ const db = await this.getDB();
1393
+ const rows = await this.getCommandsByTestId(testId);
1394
+ const tx = db.transaction("commands", "readwrite");
1395
+ await Promise.all(rows.map((r) => tx.store.delete(r.id)));
1396
+ await tx.done;
1397
+ }
1398
+ // ── Interceptors ──────────────────────────────────────────────────────────
1399
+ async insertInterceptors(interceptors, testId) {
1400
+ const db = await this.getDB();
1401
+ await Promise.all(
1402
+ interceptors.map((interceptor) => db.add("interceptors", { interceptor, testId, createdAt: Date.now() }))
1403
+ );
1404
+ }
1405
+ async getInterceptorsByTestId(testId) {
1406
+ const db = await this.getDB();
1407
+ return db.getAllFromIndex("interceptors", "testId", testId);
1408
+ }
1409
+ async getInterceptorStrings(testId) {
1410
+ return (await this.getInterceptorsByTestId(testId)).map((r) => r.interceptor).filter(Boolean);
1411
+ }
1412
+ async deleteInterceptorsByTestId(testId) {
1413
+ const db = await this.getDB();
1414
+ const rows = await this.getInterceptorsByTestId(testId);
1415
+ const tx = db.transaction("interceptors", "readwrite");
1416
+ await Promise.all(rows.map((r) => tx.store.delete(r.id)));
1417
+ await tx.done;
1418
+ }
1419
+ // ── Configuration ─────────────────────────────────────────────────────────
1420
+ async setConfig(config) {
1421
+ const db = await this.getDB();
1422
+ const records = await db.getAll("configuration");
1423
+ const current = records[0] ?? {};
1424
+ const merged = { ...current, ...config };
1425
+ if (current.id) {
1426
+ await db.put("configuration", merged);
1427
+ } else {
1428
+ await db.add("configuration", merged);
1429
+ }
1430
+ }
1431
+ async setConfigKey(key, value) {
1432
+ return this.setConfig({ [key]: value });
1433
+ }
1434
+ async getConfig(key) {
1435
+ const db = await this.getDB();
1436
+ const records = await db.getAll("configuration");
1437
+ if (!records.length) return null;
1438
+ const config = records[0];
1439
+ return Object.prototype.hasOwnProperty.call(config, key) ? { [key]: config[key] } : null;
1440
+ }
1441
+ async getGeneralConfig() {
1442
+ const db = await this.getDB();
1443
+ const records = await db.getAll("configuration");
1444
+ return records[0] ?? null;
1445
+ }
1446
+ // ── Bulk operations ───────────────────────────────────────────────────────
1447
+ async clearAllData() {
1448
+ const db = await this.getDB();
1449
+ await Promise.all([
1450
+ db.clear("tests"),
1451
+ db.clear("commands"),
1452
+ db.clear("interceptors")
1453
+ ]);
1454
+ }
1455
+ async ingestFileData(tests, interceptors) {
1456
+ await Promise.all([
1457
+ this.bulkInsertWithoutId("tests", tests),
1458
+ this.bulkInsertWithoutId("interceptors", interceptors)
1459
+ ]);
1460
+ }
1461
+ async requestDirectoryPermissions() {
1462
+ if (!("showDirectoryPicker" in window)) {
1463
+ throw new Error("File System Access API not supported");
1464
+ }
1465
+ const dirHandle = await window.showDirectoryPicker();
1466
+ await this.setConfigKey("cypressDirectoryHandle", dirHandle);
1467
+ await this.setConfigKey("allowReadWriteFiles", "true");
1468
+ }
1469
+ async bulkInsertWithoutId(store, items) {
1470
+ if (!Array.isArray(items)) return;
1471
+ const db = await this.getDB();
1472
+ for (const item of items) {
1473
+ const { id: _id, ...rest } = item;
1474
+ await db.add(store, rest);
1475
+ }
1476
+ }
1477
+ };
1478
+ var persistenceService = new PersistenceService();
1479
+
1480
+ // src/services/http-monitor.ts
1481
+ var INTERCEPTED_METHODS = ["GET", "POST", "PUT"];
1482
+ function generateAlias(method, url) {
1483
+ try {
1484
+ const u = new URL(url, "http://localhost");
1485
+ const path = u.pathname.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/(^-)|(-$)/g, "");
1486
+ return `${method.toLowerCase()}-${path}`;
1487
+ } catch {
1488
+ return `${method.toLowerCase()}-intercepted-request`;
1489
+ }
1490
+ }
1491
+ function buildValidations(base, obj) {
1492
+ return Object.keys(obj).filter((key) => key !== "id" && key !== "uid").map((key) => `expect(${base}.${key}).to.equal(${JSON.stringify(obj[key])});`).join("\n");
1493
+ }
1494
+ function buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody) {
1495
+ if (extendedHttp && method === "GET" && responseBody) {
1496
+ const validations = buildValidations("interception.response.body", responseBody);
1497
+ return `cy.wait('@${alias}').then((interception) => {
1498
+ if (interception.response) {
1499
+ ${validations}
1500
+ }
1501
+ })`;
1502
+ }
1503
+ if (extendedHttp && (method === "POST" || method === "PUT") && requestBody) {
1504
+ const validations = buildValidations("interception.request.body", requestBody);
1505
+ return `cy.wait('@${alias}').then((interception) => {
1506
+ ${validations}
1507
+ })`;
1508
+ }
1509
+ return `cy.wait('@${alias}').then((interception) => { })`;
1510
+ }
1511
+ function parseJsonObject(text) {
1512
+ try {
1513
+ const parsed = JSON.parse(text);
1514
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1515
+ return parsed;
1516
+ }
1517
+ } catch {
1518
+ }
1519
+ return null;
1520
+ }
1521
+ function parseRequestBody(init) {
1522
+ const body = init?.body;
1523
+ if (typeof body === "string") return parseJsonObject(body);
1524
+ return null;
1525
+ }
1526
+ function resolveUrl(input) {
1527
+ if (typeof input === "string") return input;
1528
+ if (input instanceof URL) return input.href;
1529
+ return input.url;
1530
+ }
1531
+ function resolveMethod(input, init) {
1532
+ if (init?.method) return init.method.toUpperCase();
1533
+ if (input instanceof Request) return input.method.toUpperCase();
1534
+ return "GET";
1535
+ }
1536
+ var HttpMonitor = class {
1537
+ constructor(recording) {
1538
+ this.recording = recording;
1539
+ }
1540
+ recording;
1541
+ originalFetch = null;
1542
+ originalXHR = null;
1543
+ install() {
1544
+ this.installFetch();
1545
+ this.installXhr();
1546
+ }
1547
+ uninstall() {
1548
+ this.uninstallFetch();
1549
+ this.uninstallXhr();
1550
+ }
1551
+ isExtendedHttpEnabled() {
1552
+ return localStorage.getItem("extendedHttpCommands") === "true";
1553
+ }
1554
+ installFetch() {
1555
+ if (this.originalFetch) return;
1556
+ const originalFetch = window.fetch;
1557
+ this.originalFetch = originalFetch;
1558
+ window.fetch = async (input, init) => {
1559
+ const response = await originalFetch(input, init);
1560
+ try {
1561
+ await this.handleFetchInterception(input, init, response.clone());
1562
+ } catch {
1563
+ }
1564
+ return response;
1565
+ };
1566
+ }
1567
+ uninstallFetch() {
1568
+ if (!this.originalFetch) return;
1569
+ window.fetch = this.originalFetch;
1570
+ this.originalFetch = null;
1571
+ }
1572
+ installXhr() {
1573
+ if (this.originalXHR) return;
1574
+ this.originalXHR = window.XMLHttpRequest;
1575
+ const OrigXHR = this.originalXHR;
1576
+ const handleXhrInterception = this.handleXhrInterception.bind(this);
1577
+ window.XMLHttpRequest = class extends OrigXHR {
1578
+ _xhrMethod = "GET";
1579
+ _xhrUrl = "";
1580
+ _xhrRequestBody = null;
1581
+ open(method, url, async = true, user, password) {
1582
+ this._xhrMethod = method.toUpperCase();
1583
+ this._xhrUrl = url instanceof URL ? url.href : url;
1584
+ super.open(method, url, async, user, password);
1585
+ }
1586
+ send(body) {
1587
+ if (typeof body === "string") {
1588
+ this._xhrRequestBody = parseJsonObject(body);
1589
+ }
1590
+ this.addEventListener("load", () => {
1591
+ try {
1592
+ handleXhrInterception(
1593
+ this._xhrMethod,
1594
+ this._xhrUrl,
1595
+ this._xhrRequestBody,
1596
+ this.responseText
1597
+ );
1598
+ } catch {
1599
+ }
1600
+ });
1601
+ super.send(body);
1602
+ }
1603
+ };
1604
+ }
1605
+ uninstallXhr() {
1606
+ if (!this.originalXHR) return;
1607
+ window.XMLHttpRequest = this.originalXHR;
1608
+ this.originalXHR = null;
1609
+ }
1610
+ async handleFetchInterception(input, init, responseClone) {
1611
+ const method = resolveMethod(input, init);
1612
+ if (!INTERCEPTED_METHODS.includes(method)) return;
1613
+ const url = resolveUrl(input);
1614
+ const alias = generateAlias(method, url);
1615
+ this.recording.registerInterceptor(method, url, alias);
1616
+ const extendedHttp = this.isExtendedHttpEnabled();
1617
+ const requestBody = extendedHttp ? parseRequestBody(init) : null;
1618
+ let responseBody = null;
1619
+ if (extendedHttp) {
1620
+ try {
1621
+ responseBody = parseJsonObject(await responseClone.text());
1622
+ } catch {
1623
+ }
1624
+ }
1625
+ const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
1626
+ this.recording.addCommand(cmd);
1627
+ }
1628
+ handleXhrInterception(method, url, requestBody, responseText) {
1629
+ if (!INTERCEPTED_METHODS.includes(method)) return;
1630
+ const alias = generateAlias(method, url);
1631
+ this.recording.registerInterceptor(method, url, alias);
1632
+ const extendedHttp = this.isExtendedHttpEnabled();
1633
+ const responseBody = extendedHttp ? parseJsonObject(responseText) : null;
1634
+ const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
1635
+ this.recording.addCommand(cmd);
1636
+ }
1637
+ };
1638
+
1639
+ // src/utils/styles.utils.ts
1640
+ var SCROLLBAR_STYLES = `
1641
+ .swal2-popup::-webkit-scrollbar,
1642
+ .swal2-html-container::-webkit-scrollbar,
1643
+ .swal2-content::-webkit-scrollbar,
1644
+ .swal2-container::-webkit-scrollbar {
1645
+ width: 5px;
1646
+ height: 5px;
1647
+ background: transparent;
1648
+ }
1649
+ .swal2-popup::-webkit-scrollbar-thumb,
1650
+ .swal2-html-container::-webkit-scrollbar-thumb,
1651
+ .swal2-content::-webkit-scrollbar-thumb,
1652
+ .swal2-container::-webkit-scrollbar-thumb {
1653
+ background: #30363d;
1654
+ border-radius: 3px;
1655
+ }
1656
+ .swal2-popup::-webkit-scrollbar-thumb:hover,
1657
+ .swal2-html-container::-webkit-scrollbar-thumb:hover,
1658
+ .swal2-content::-webkit-scrollbar-thumb:hover {
1659
+ background: #484f58;
1660
+ }
1661
+ .swal2-popup::-webkit-scrollbar-track,
1662
+ .swal2-html-container::-webkit-scrollbar-track,
1663
+ .swal2-content::-webkit-scrollbar-track,
1664
+ .swal2-container::-webkit-scrollbar-track {
1665
+ background: transparent;
1666
+ }
1667
+ .swal2-popup, .swal2-html-container, .swal2-content, .swal2-container {
1668
+ scrollbar-width: thin;
1669
+ scrollbar-color: #30363d transparent;
1670
+ }
1671
+ `;
1672
+ var LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES = `
1673
+ .swal2-container, .swal2-popup {
1674
+ z-index: 99999 !important;
1675
+ }
1676
+ .swal2-container {
1677
+ padding: 0 !important;
1678
+ align-items: center !important;
1679
+ justify-content: center !important;
1680
+ }
1681
+ .swal2-popup {
1682
+ background: #161b22 !important;
1683
+ color: #e6edf3 !important;
1684
+ border-radius: 12px !important;
1685
+ box-shadow: 0 24px 64px rgba(0,0,0,0.72), 0 0 0 1px #30363d !important;
1686
+ border: none !important;
1687
+ padding: 0 !important;
1688
+ min-width: 400px;
1689
+ max-width: 90vw;
1690
+ min-height: 200px;
1691
+ max-height: 90vh;
1692
+ }
1693
+ /* Override SweetAlert2 v11's display:grid on .swal2-popup.swal2-modal */
1694
+ .swal2-popup.swal2-modal {
1695
+ display: flex !important;
1696
+ flex-direction: column !important;
1697
+ align-items: stretch !important;
1698
+ overflow: hidden !important;
1699
+ }
1700
+ .swal2-header {
1701
+ flex-shrink: 0 !important;
1702
+ flex-grow: 0 !important;
1703
+ overflow: hidden !important;
1704
+ }
1705
+ .swal2-title {
1706
+ color: #e6edf3 !important;
1707
+ font-weight: 600 !important;
1708
+ font-size: 14px !important;
1709
+ background: #161b22;
1710
+ padding: 14px 48px 13px 18px !important;
1711
+ margin: 0 !important;
1712
+ border-bottom: 1px solid #21262d;
1713
+ text-align: left !important;
1714
+ letter-spacing: 0.1px;
1715
+ white-space: nowrap !important;
1716
+ overflow: hidden !important;
1717
+ text-overflow: ellipsis !important;
1718
+ }
1719
+ .swal2-close {
1720
+ position: absolute !important;
1721
+ top: 10px !important;
1722
+ right: 12px !important;
1723
+ color: #8b949e !important;
1724
+ font-size: 1.1rem !important;
1725
+ z-index: 1 !important;
1726
+ border-radius: 6px !important;
1727
+ width: 28px !important;
1728
+ height: 28px !important;
1729
+ line-height: 28px !important;
1730
+ transition: background 0.15s, color 0.15s !important;
1731
+ }
1732
+ .swal2-close:hover {
1733
+ background: #21262d !important;
1734
+ color: #e6edf3 !important;
1735
+ }
1736
+ /* In SweetAlert2 v11, .swal2-html-container is a direct child of .swal2-popup */
1737
+ .swal2-html-container {
1738
+ flex: 1 !important;
1739
+ min-height: 0 !important;
1740
+ background: #161b22;
1741
+ padding: 0 !important;
1742
+ margin: 0 !important;
1743
+ width: 100%;
1744
+ display: flex !important;
1745
+ flex-direction: column !important;
1746
+ align-items: stretch;
1747
+ box-sizing: border-box;
1748
+ overflow: hidden !important;
1749
+ }
1750
+ /* The single wrapper div we inject inside each modal fills the container */
1751
+ .swal2-html-container > div {
1752
+ flex: 1 !important;
1753
+ min-height: 0 !important;
1754
+ display: flex !important;
1755
+ flex-direction: column !important;
1756
+ overflow: hidden !important;
1757
+ }
1758
+ .swal2-actions, .swal2-footer {
1759
+ flex-shrink: 0 !important;
1760
+ flex-grow: 0 !important;
1761
+ }
1762
+ `;
1763
+ function injectStyles(css, id) {
1764
+ if (document.getElementById(id)) return;
1765
+ const style = document.createElement("style");
1766
+ style.id = id;
1767
+ style.innerHTML = css;
1768
+ document.head.appendChild(style);
1769
+ }
1770
+
1771
+ // src/utils/modal.utils.ts
1772
+ function makeModalResizable(modal, options) {
1773
+ if (!modal || modal.querySelector(".modal-resizer")) return () => {
1774
+ };
1775
+ const rect = modal.getBoundingClientRect();
1776
+ modal.style.position = "fixed";
1777
+ modal.style.top = `${rect.top}px`;
1778
+ modal.style.left = `${rect.left}px`;
1779
+ modal.style.width = `${rect.width}px`;
1780
+ modal.style.margin = "0";
1781
+ modal.style.resize = "both";
1782
+ modal.style.overflow = "hidden";
1783
+ modal.style.minWidth = (options?.minWidth ?? 320) + "px";
1784
+ modal.style.minHeight = (options?.minHeight ?? 180) + "px";
1785
+ const resizer = document.createElement("div");
1786
+ resizer.className = "modal-resizer";
1787
+ resizer.style.position = "absolute";
1788
+ resizer.style.width = "16px";
1789
+ resizer.style.height = "16px";
1790
+ resizer.style.right = "2px";
1791
+ resizer.style.bottom = "2px";
1792
+ resizer.style.cursor = "nwse-resize";
1793
+ resizer.style.background = "rgba(0,0,0,0.1)";
1794
+ resizer.style.zIndex = "10";
1795
+ modal.appendChild(resizer);
1796
+ let isResizing = false;
1797
+ let lastX = 0;
1798
+ let lastY = 0;
1799
+ const mouseMove = (e) => {
1800
+ if (!isResizing) return;
1801
+ const dx = e.clientX - lastX;
1802
+ const dy = e.clientY - lastY;
1803
+ lastX = e.clientX;
1804
+ lastY = e.clientY;
1805
+ const rect2 = modal.getBoundingClientRect();
1806
+ modal.style.width = rect2.width + dx + "px";
1807
+ modal.style.height = rect2.height + dy + "px";
1808
+ };
1809
+ const mouseUp = () => {
1810
+ isResizing = false;
1811
+ document.body.style.userSelect = "";
1812
+ };
1813
+ resizer.addEventListener("mousedown", (e) => {
1814
+ e.preventDefault();
1815
+ isResizing = true;
1816
+ lastX = e.clientX;
1817
+ lastY = e.clientY;
1818
+ document.body.style.userSelect = "none";
1819
+ });
1820
+ window.addEventListener("mousemove", mouseMove);
1821
+ window.addEventListener("mouseup", mouseUp);
1822
+ return () => {
1823
+ window.removeEventListener("mousemove", mouseMove);
1824
+ window.removeEventListener("mouseup", mouseUp);
1825
+ if (resizer.parentNode) resizer.parentNode.removeChild(resizer);
1826
+ };
1827
+ }
1828
+ function applyDraggable(swal, dragArea) {
1829
+ let isDragging = false;
1830
+ let offsetX = 0;
1831
+ let offsetY = 0;
1832
+ dragArea.style.cursor = "move";
1833
+ dragArea.onmousedown = (e) => {
1834
+ isDragging = true;
1835
+ const rect = swal.getBoundingClientRect();
1836
+ offsetX = e.clientX - rect.left;
1837
+ offsetY = e.clientY - rect.top;
1838
+ document.onmousemove = (ev) => {
1839
+ if (isDragging) {
1840
+ swal.style.position = "fixed";
1841
+ swal.style.margin = "0";
1842
+ swal.style.left = `${ev.clientX - offsetX}px`;
1843
+ swal.style.top = `${ev.clientY - offsetY}px`;
1844
+ }
1845
+ };
1846
+ document.onmouseup = () => {
1847
+ isDragging = false;
1848
+ document.onmousemove = null;
1849
+ document.onmouseup = null;
1850
+ };
1851
+ };
1852
+ }
1853
+ function resolveDragArea(swal) {
1854
+ return swal.querySelector(".swal2-header") ?? swal.querySelector(".swal2-title");
1855
+ }
1856
+ function makeSwalDraggable() {
1857
+ const popups = document.querySelectorAll(".swal2-popup");
1858
+ if (!popups.length) return;
1859
+ const swal = popups[popups.length - 1];
1860
+ const dragArea = resolveDragArea(swal);
1861
+ if (!dragArea) return;
1862
+ applyDraggable(swal, dragArea);
1863
+ }
1864
+ function makeSwalDraggableByContentId(contentId) {
1865
+ const content = document.getElementById(contentId);
1866
+ if (!content) {
1867
+ makeSwalDraggable();
1868
+ return;
1869
+ }
1870
+ const swal = content.closest(".swal2-popup") ?? document.querySelector(".swal2-popup");
1871
+ if (!swal) return;
1872
+ const dragArea = resolveDragArea(swal);
1873
+ if (!dragArea) return;
1874
+ applyDraggable(swal, dragArea);
1875
+ }
1876
+ function setSwal2DataCyAttribute(dataCy = "lib-e2e-cypress-for-dummys") {
1877
+ document.querySelector(".swal2-container")?.setAttribute("data-cy", dataCy);
1878
+ document.querySelector(".swal2-html-container")?.setAttribute("data-cy", dataCy);
1879
+ document.querySelector(".swal2-title")?.setAttribute("data-cy", dataCy);
1880
+ }
1881
+
1882
+ // src/utils/toast.utils.ts
1883
+ function showToast(message, isSuccess = true) {
1884
+ const toast = document.createElement("div");
1885
+ toast.textContent = message;
1886
+ toast.style.position = "fixed";
1887
+ toast.style.top = "30px";
1888
+ toast.style.right = "30px";
1889
+ toast.style.padding = "16px 24px";
1890
+ toast.style.background = isSuccess ? "#4caf50" : "#f44336";
1891
+ toast.style.color = "#fff";
1892
+ toast.style.borderRadius = "8px";
1893
+ toast.style.boxShadow = "0 2px 8px rgba(0,0,0,0.2)";
1894
+ toast.style.zIndex = "9999";
1895
+ toast.style.fontSize = "1rem";
1896
+ toast.style.fontWeight = "bold";
1897
+ toast.style.pointerEvents = "none";
1898
+ document.body.appendChild(toast);
1899
+ setTimeout(() => toast.remove(), 3e3);
1900
+ }
1901
+
1902
+ // src/components/test-previsualizer/test-previsualizer.styles.ts
1903
+ var TEST_PREVISUALIZER_STYLES = `
1904
+ :host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
1905
+ * { box-sizing: border-box; }
1906
+ .toolbar {
1907
+ display: flex; gap: 6px; padding: 10px 12px; flex-shrink: 0;
1908
+ background: #161b22; border-bottom: 1px solid #21262d;
1909
+ }
1910
+ button {
1911
+ padding: 5px 12px; border: none; border-radius: 6px; cursor: pointer;
1912
+ font-size: 11px; font-weight: 500; background: #21262d; color: #8b949e;
1913
+ transition: background 0.15s, color 0.12s; letter-spacing: 0.1px;
1914
+ }
1915
+ button:hover { background: #30363d; color: #e6edf3; }
1916
+ button.active { background: #2f81f7; color: #fff; }
1917
+ .section { padding: 10px 12px; flex-shrink: 0; }
1918
+ .section.section-cmds { flex: 1; min-height: 0; display: flex; flex-direction: column; }
1919
+ .section-title {
1920
+ font-size: 10px; font-weight: 600; color: #484f58; text-transform: uppercase;
1921
+ letter-spacing: 0.8px; margin-bottom: 7px; flex-shrink: 0;
1922
+ }
1923
+ .list {
1924
+ overflow-y: auto; background: #0d1117;
1925
+ border-radius: 8px; padding: 6px 8px;
1926
+ border: 1px solid #21262d;
1927
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
1928
+ }
1929
+ .section-cmds .list { flex: 1; min-height: 0; }
1930
+ .list::-webkit-scrollbar { width: 4px; }
1931
+ .list::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
1932
+ .item {
1933
+ display: flex; align-items: flex-start; gap: 6px;
1934
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; font-size: 11px;
1935
+ color: #c9d1d9; padding: 4px 8px; border-radius: 4px;
1936
+ word-break: break-all; line-height: 1.65;
1937
+ border-left: 2px solid transparent;
1938
+ transition: background 0.1s, border-color 0.1s;
1939
+ }
1940
+ .item:hover { background: #161b22; border-left-color: #2f81f7; }
1941
+ .cmd-text { flex: 1; }
1942
+ .item-actions {
1943
+ display: flex; gap: 3px; flex-shrink: 0;
1944
+ opacity: 0; transition: opacity 0.15s;
1945
+ }
1946
+ .item:hover .item-actions { opacity: 1; }
1947
+ .btn-step {
1948
+ width: 20px; height: 20px; border: none; border-radius: 3px; cursor: pointer;
1949
+ font-size: 12px; background: transparent; color: #484f58;
1950
+ transition: background 0.12s, color 0.12s;
1951
+ display: flex; align-items: center; justify-content: center;
1952
+ padding: 0; line-height: 1;
1953
+ }
1954
+ .btn-step:hover { background: #30363d; color: #e6edf3; }
1955
+ .btn-step-del:hover { background: rgba(248,81,73,0.15); color: #f85149; }
1956
+ .empty { color: #484f58; font-size: 12px; padding: 20px 8px; text-align: center; }
1957
+ `;
1958
+
1959
+ // src/components/test-previsualizer/test-previsualizer.template.ts
1960
+ function renderTestPrevisualizer(commands, interceptors, showInterceptors, editable, t) {
1961
+ const editControls = (idx, total) => editable ? `
1962
+ <span class="item-actions">
1963
+ <button class="btn-step" data-move-up="${idx}" title="${t("TEST_PREVISUALIZER.UP_TITLE")}" ${idx === 0 ? 'disabled style="opacity:.3"' : ""}>\u2191</button>
1964
+ <button class="btn-step" data-move-dn="${idx}" title="${t("TEST_PREVISUALIZER.DOWN_TITLE")}" ${idx === total - 1 ? 'disabled style="opacity:.3"' : ""}>\u2193</button>
1965
+ <button class="btn-step btn-step-del" data-del="${idx}" title="${t("TEST_PREVISUALIZER.DEL_TITLE")}">\u2715</button>
1966
+ </span>` : "";
1967
+ const cmdItems = commands.length ? commands.map((c, i) => `
1968
+ <div class="item">
1969
+ <span class="cmd-text">${escHtml(c)}</span>
1970
+ ${editControls(i, commands.length)}
1971
+ </div>`).join("") : `<div class="empty">${t("TEST_PREVISUALIZER.NO_CMDS_YET")}</div>`;
1972
+ const icpEditControls = (idx) => editable ? `<span class="item-actions"><button class="btn-step btn-step-del" data-del-icp="${idx}" title="${t("TEST_PREVISUALIZER.DEL_TITLE")}">\u2715</button></span>` : "";
1973
+ const icpSection = showInterceptors ? `<div class="section" data-section="interceptors">
1974
+ <div class="section-title">${t("TEST_PREVISUALIZER.INTERCEPTORS")}</div>
1975
+ <div class="list">
1976
+ ${interceptors.length ? interceptors.map((ic, idx) => `
1977
+ <div class="item">
1978
+ <span class="cmd-text">${escHtml(ic)}</span>
1979
+ ${icpEditControls(idx)}
1980
+ </div>`).join("") : `<div class="empty">${t("TEST_PREVISUALIZER.NO_ICP_SHORT")}</div>`}
1981
+ </div>
1982
+ <button style="margin-top:6px" data-action="copy-icp">${t("TEST_PREVISUALIZER.COPY_ICP_BTN")}</button>
1983
+ </div>` : "";
1984
+ return `
1985
+ <div class="toolbar">
1986
+ <button data-action="copy">${t("TEST_PREVISUALIZER.COPY_CMDS_BTN")}</button>
1987
+ <button data-action="toggle-icp" class="${showInterceptors ? "active" : ""}">
1988
+ ${showInterceptors ? t("TEST_PREVISUALIZER.HIDE_INTERCEPTORS") : t("TEST_PREVISUALIZER.SHOW_INTERCEPTORS")} (${interceptors.length})
1989
+ </button>
1990
+ </div>
1991
+ <div class="section section-cmds">
1992
+ <div class="section-title">${t("TEST_PREVISUALIZER.SECTION_COMMANDS")} (${commands.length})</div>
1993
+ <div class="list" data-ref="cmds">${cmdItems}</div>
1994
+ </div>
1995
+ ${icpSection}`;
1996
+ }
1997
+
1998
+ // src/components/test-previsualizer/test-previsualizer.ts
1999
+ var TestPrevisualizerElement = class extends HTMLElement {
2000
+ shadow;
2001
+ _commands = [];
2002
+ _interceptors = [];
2003
+ _showInterceptors = false;
2004
+ editable = false;
2005
+ translation = translationService;
2006
+ constructor() {
2007
+ super();
2008
+ this.shadow = this.attachShadow({ mode: "open" });
2009
+ }
2010
+ connectedCallback() {
2011
+ this.render();
2012
+ }
2013
+ get commands() {
2014
+ return this._commands;
2015
+ }
2016
+ set commands(v) {
2017
+ this._commands = v;
2018
+ this.render();
2019
+ }
2020
+ get interceptors() {
2021
+ return this._interceptors;
2022
+ }
2023
+ set interceptors(v) {
2024
+ this._interceptors = v;
2025
+ this.render();
2026
+ }
2027
+ get showInterceptors() {
2028
+ return this._showInterceptors;
2029
+ }
2030
+ toggleInterceptors() {
2031
+ this._showInterceptors = !this._showInterceptors;
2032
+ this.render();
2033
+ }
2034
+ copyToClipboard() {
2035
+ const text = this._commands.join("\n");
2036
+ if (!text) return;
2037
+ navigator.clipboard?.writeText(text);
2038
+ }
2039
+ copyInterceptorsToClipboard() {
2040
+ const text = this._interceptors.join("\n");
2041
+ if (!text) return;
2042
+ navigator.clipboard?.writeText(text);
2043
+ }
2044
+ t(key) {
2045
+ return this.translation.translate(key);
2046
+ }
2047
+ dispatchDelete(index) {
2048
+ this.dispatchEvent(new CustomEvent("deletecommand", { detail: index, bubbles: true, composed: true }));
2049
+ }
2050
+ dispatchMove(from, to) {
2051
+ this.dispatchEvent(new CustomEvent("movecommand", { detail: { from, to }, bubbles: true, composed: true }));
2052
+ }
2053
+ dispatchDeleteInterceptor(index) {
2054
+ this.dispatchEvent(new CustomEvent("deleteinterceptor", { detail: index, bubbles: true, composed: true }));
2055
+ }
2056
+ render() {
2057
+ this.shadow.innerHTML = `<style>${TEST_PREVISUALIZER_STYLES}</style>${renderTestPrevisualizer(
2058
+ this._commands,
2059
+ this._interceptors,
2060
+ this._showInterceptors,
2061
+ this.editable,
2062
+ this.t.bind(this)
2063
+ )}`;
2064
+ const cmdsEl = this.shadow.querySelector('[data-ref="cmds"]');
2065
+ if (cmdsEl) cmdsEl.scrollTop = cmdsEl.scrollHeight;
2066
+ this.shadow.querySelector('[data-action="copy"]')?.addEventListener("click", () => this.copyToClipboard());
2067
+ this.shadow.querySelector('[data-action="toggle-icp"]')?.addEventListener("click", () => this.toggleInterceptors());
2068
+ this.shadow.querySelector('[data-action="copy-icp"]')?.addEventListener("click", () => this.copyInterceptorsToClipboard());
2069
+ if (this.editable) {
2070
+ this.shadow.querySelectorAll("[data-del]").forEach((btn) => {
2071
+ btn.addEventListener("click", (e) => {
2072
+ e.stopPropagation();
2073
+ this.dispatchDelete(Number(btn.dataset["del"]));
2074
+ });
2075
+ });
2076
+ this.shadow.querySelectorAll("[data-move-up]").forEach((btn) => {
2077
+ btn.addEventListener("click", (e) => {
2078
+ e.stopPropagation();
2079
+ const idx = Number(btn.dataset["moveUp"]);
2080
+ this.dispatchMove(idx, idx - 1);
2081
+ });
2082
+ });
2083
+ this.shadow.querySelectorAll("[data-move-dn]").forEach((btn) => {
2084
+ btn.addEventListener("click", (e) => {
2085
+ e.stopPropagation();
2086
+ const idx = Number(btn.dataset["moveDn"]);
2087
+ this.dispatchMove(idx, idx + 1);
2088
+ });
2089
+ });
2090
+ this.shadow.querySelectorAll("[data-del-icp]").forEach((btn) => {
2091
+ btn.addEventListener("click", (e) => {
2092
+ e.stopPropagation();
2093
+ this.dispatchDeleteInterceptor(Number(btn.dataset["delIcp"]));
2094
+ });
2095
+ });
2096
+ }
2097
+ }
2098
+ };
2099
+ if (!customElements.get("test-previsualizer")) {
2100
+ customElements.define("test-previsualizer", TestPrevisualizerElement);
2101
+ }
2102
+
2103
+ // src/components/save-test/save-test.styles.ts
2104
+ var SAVE_TEST_STYLES = `
2105
+ :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
2106
+ * { box-sizing: border-box; }
2107
+ .container { padding: 24px 28px; text-align: center; }
2108
+ p { margin: 0 0 20px; font-size: 14px; color: #8b949e; line-height: 1.5; }
2109
+ .btn-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 16px; }
2110
+ button {
2111
+ padding: 7px 16px; border: 1px solid #30363d; border-radius: 6px; cursor: pointer;
2112
+ font-size: 12px; font-weight: 500; transition: filter 0.15s, transform 0.1s;
2113
+ letter-spacing: 0.1px;
2114
+ }
2115
+ button:hover { filter: brightness(1.1); }
2116
+ button:active { transform: scale(0.97); }
2117
+ .btn-primary { background: #2f81f7; color: #fff; }
2118
+ .btn-success { background: #3fb950; color: #fff; }
2119
+ .btn-danger { background: transparent; color: #f85149; border: 1px solid rgba(248,81,73,0.5); }
2120
+ .btn-danger:hover { background: rgba(248,81,73,0.08); filter: none; }
2121
+ input[type="text"] {
2122
+ width: 100%; padding: 10px 14px; border: 1px solid #30363d;
2123
+ border-radius: 8px; background: #0d1117; color: #e6edf3;
2124
+ font-size: 14px; outline: none; margin-bottom: 4px;
2125
+ transition: border-color 0.15s, box-shadow 0.15s;
2126
+ }
2127
+ input[type="text"]:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.15); }
2128
+ input[type="text"]::placeholder { color: #484f58; }
2129
+ .tag-label {
2130
+ font-size: 11px; color: #484f58; text-align: left; margin-bottom: 5px; display: block;
2131
+ }
2132
+ .tag-input-row { display: flex; gap: 6px; margin-bottom: 8px; }
2133
+ .tag-input-row input[type="text"] {
2134
+ font-size: 12px; padding: 6px 10px; margin: 0;
2135
+ }
2136
+ .btn-tag-add {
2137
+ padding: 6px 12px; font-size: 12px; background: #21262d; color: #8b949e;
2138
+ border: 1px solid #30363d; border-radius: 6px; cursor: pointer;
2139
+ transition: background 0.15s, color 0.12s; white-space: nowrap; flex-shrink: 0;
2140
+ }
2141
+ .btn-tag-add:hover { background: #30363d; color: #e6edf3; filter: none; }
2142
+ .chips { display: flex; flex-wrap: wrap; gap: 5px; min-height: 24px; text-align: left; }
2143
+ .chip {
2144
+ display: inline-flex; align-items: center; gap: 5px;
2145
+ background: rgba(47,129,247,0.15); color: #2f81f7;
2146
+ border: 1px solid rgba(47,129,247,0.3); border-radius: 20px;
2147
+ padding: 2px 10px 2px 10px; font-size: 11px; font-weight: 500;
2148
+ }
2149
+ .chip-del {
2150
+ cursor: pointer; color: #8b949e; font-size: 12px; line-height: 1;
2151
+ transition: color 0.12s; padding: 0; background: none; border: none;
2152
+ }
2153
+ .chip-del:hover { color: #f85149; filter: none; }
2154
+ `;
2155
+
2156
+ // src/components/save-test/save-test.template.ts
2157
+ function renderSaveTestAsk(t) {
2158
+ return `
2159
+ <div class="container">
2160
+ <p>${t("SAVE_TEST.ASK")}</p>
2161
+ <div class="btn-row">
2162
+ <button class="btn-primary" id="btn-yes">${t("SAVE_TEST.YES_CONTINUE")}</button>
2163
+ <button class="btn-danger" id="btn-no">${t("SAVE_TEST.NO_DISCARD")}</button>
2164
+ </div>
2165
+ </div>`;
2166
+ }
2167
+ function renderSaveTestDesc(description, tags, t) {
2168
+ const chipsHtml = tags.map(
2169
+ (tag) => `<span class="chip">${escHtml(tag)}<button class="chip-del" data-tag="${escAttr(tag)}" title="${t("SAVE_TEST.REMOVE_TAG_TITLE")}">\u2715</button></span>`
2170
+ ).join("");
2171
+ return `
2172
+ <div class="container">
2173
+ <p>${t("SAVE_TEST.DESC_LABEL")} (<code>it()</code>):</p>
2174
+ <input id="desc-input" type="text" placeholder="${t("SAVE_TEST.DESC_PLACEHOLDER")}"
2175
+ value="${escAttr(description)}" autocomplete="off" />
2176
+ <span class="tag-label">${t("SAVE_TEST.TAGS_LABEL")}</span>
2177
+ <div class="tag-input-row">
2178
+ <input id="tag-input" type="text" placeholder="${t("SAVE_TEST.TAGS_PLACEHOLDER")}" autocomplete="off" />
2179
+ <button class="btn-tag-add" id="btn-add-tag">${t("SAVE_TEST.ADD_TAG")}</button>
2180
+ </div>
2181
+ <div class="chips" id="chips-container">${chipsHtml || `<span style="color:#484f58;font-size:11px">${t("SAVE_TEST.NO_TAGS")}</span>`}</div>
2182
+ <div class="btn-row">
2183
+ <button class="btn-primary" id="btn-confirm">${t("SAVE_TEST.SAVE_BTN")}</button>
2184
+ <button class="btn-success" id="btn-export">${t("SAVE_TEST.SAVE_AND_EDIT")}</button>
2185
+ <button class="btn-danger" id="btn-cancel">${t("SAVE_TEST.CANCEL")}</button>
2186
+ </div>
2187
+ </div>`;
2188
+ }
2189
+
2190
+ // src/components/save-test/save-test.ts
2191
+ var SaveTestElement = class extends HTMLElement {
2192
+ shadow;
2193
+ _step = "ask";
2194
+ description = "";
2195
+ tags = [];
2196
+ translation = translationService;
2197
+ constructor() {
2198
+ super();
2199
+ this.shadow = this.attachShadow({ mode: "open" });
2200
+ }
2201
+ connectedCallback() {
2202
+ this.render();
2203
+ }
2204
+ get step() {
2205
+ return this._step;
2206
+ }
2207
+ askSave() {
2208
+ this._step = "desc";
2209
+ this.render();
2210
+ }
2211
+ confirmSave() {
2212
+ this.dispatch("savetest", { description: this.description.trim(), tags: [...this.tags] });
2213
+ }
2214
+ confirmSaveAndExport() {
2215
+ this.dispatch("saveandexport", { description: this.description.trim(), tags: [...this.tags] });
2216
+ }
2217
+ cancel() {
2218
+ this.dispatch("savetest", { description: null, tags: [] });
2219
+ }
2220
+ restartComponent() {
2221
+ this._step = "ask";
2222
+ this.description = "";
2223
+ this.tags = [];
2224
+ this.render();
2225
+ }
2226
+ addTag(tag) {
2227
+ const t = tag.trim().replace(/[,;]/g, "");
2228
+ if (t && !this.tags.includes(t)) {
2229
+ this.tags = [...this.tags, t];
2230
+ this.render();
2231
+ }
2232
+ }
2233
+ removeTag(tag) {
2234
+ this.tags = this.tags.filter((t) => t !== tag);
2235
+ this.render();
2236
+ }
2237
+ t(key) {
2238
+ return this.translation.translate(key);
2239
+ }
2240
+ dispatch(type, detail) {
2241
+ this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
2242
+ }
2243
+ render() {
2244
+ if (this._step === "ask") {
2245
+ this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestAsk(this.t.bind(this))}`;
2246
+ this.shadow.getElementById("btn-yes")?.addEventListener("click", () => this.askSave());
2247
+ this.shadow.getElementById("btn-no")?.addEventListener("click", () => this.cancel());
2248
+ } else {
2249
+ this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.tags, this.t.bind(this))}`;
2250
+ const descInput = this.shadow.getElementById("desc-input");
2251
+ const tagInput = this.shadow.getElementById("tag-input");
2252
+ descInput.addEventListener("input", () => {
2253
+ this.description = descInput.value;
2254
+ });
2255
+ const tryAddTag = () => {
2256
+ this.addTag(tagInput.value);
2257
+ tagInput.value = "";
2258
+ };
2259
+ this.shadow.getElementById("btn-add-tag")?.addEventListener("click", tryAddTag);
2260
+ tagInput.addEventListener("keydown", (e) => {
2261
+ if (e.key === "Enter" || e.key === ",") {
2262
+ e.preventDefault();
2263
+ tryAddTag();
2264
+ }
2265
+ });
2266
+ this.shadow.querySelectorAll(".chip-del").forEach((btn) => {
2267
+ btn.addEventListener("click", () => this.removeTag(btn.dataset["tag"] ?? ""));
2268
+ });
2269
+ this.shadow.getElementById("btn-confirm")?.addEventListener("click", () => this.confirmSave());
2270
+ this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.confirmSaveAndExport());
2271
+ this.shadow.getElementById("btn-cancel")?.addEventListener("click", () => this.cancel());
2272
+ setTimeout(() => descInput.focus(), 60);
2273
+ }
2274
+ }
2275
+ };
2276
+ if (!customElements.get("save-test")) {
2277
+ customElements.define("save-test", SaveTestElement);
2278
+ }
2279
+
2280
+ // src/components/test-editor/test-editor.styles.ts
2281
+ var TEST_EDITOR_STYLES = `
2282
+ :host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
2283
+ * { box-sizing: border-box; }
2284
+ .toolbar {
2285
+ display: flex; align-items: center; gap: 6px; padding: 8px 10px; flex-shrink: 0;
2286
+ background: #161b22; border-bottom: 1px solid #21262d; flex-wrap: wrap;
2287
+ }
2288
+ .tag-filter { display: flex; gap: 5px; flex-wrap: wrap; flex: 1; }
2289
+ .tag-chip {
2290
+ padding: 2px 10px; border-radius: 20px; cursor: pointer; font-size: 11px; font-weight: 500;
2291
+ border: 1px solid #30363d; background: #21262d; color: #8b949e;
2292
+ transition: background 0.12s, color 0.12s, border-color 0.12s;
2293
+ }
2294
+ .tag-chip:hover { background: #30363d; color: #e6edf3; }
2295
+ .tag-chip.active { background: rgba(47,129,247,0.2); color: #2f81f7; border-color: rgba(47,129,247,0.4); }
2296
+ .btn-select {
2297
+ padding: 4px 12px; border: 1px solid #30363d; border-radius: 6px; cursor: pointer;
2298
+ font-size: 11px; font-weight: 500; background: #21262d; color: #8b949e;
2299
+ transition: background 0.12s, color 0.12s; white-space: nowrap; flex-shrink: 0;
2300
+ }
2301
+ .btn-select:hover { background: #30363d; color: #e6edf3; }
2302
+ .btn-select.active { background: rgba(47,129,247,0.15); color: #2f81f7; border-color: rgba(47,129,247,0.3); }
2303
+ .describe-bar {
2304
+ display: flex; align-items: center; gap: 8px; padding: 8px 10px;
2305
+ background: rgba(47,129,247,0.06); border-bottom: 1px solid rgba(47,129,247,0.2);
2306
+ flex-wrap: wrap;
2307
+ }
2308
+ .describe-bar input {
2309
+ flex: 1; min-width: 150px; padding: 5px 10px; background: #0d1117;
2310
+ border: 1px solid #30363d; border-radius: 6px; color: #e6edf3;
2311
+ font-size: 12px; outline: none;
2312
+ }
2313
+ .describe-bar input:focus { border-color: #2f81f7; }
2314
+ .btn-gen-describe {
2315
+ padding: 5px 14px; border: 1px solid #2f81f7; border-radius: 6px; cursor: pointer;
2316
+ font-size: 11px; font-weight: 500; background: rgba(47,129,247,0.15); color: #2f81f7;
2317
+ transition: background 0.12s; white-space: nowrap; flex-shrink: 0;
2318
+ }
2319
+ .btn-gen-describe:hover { background: rgba(47,129,247,0.25); }
2320
+ .selected-count { font-size: 11px; color: #8b949e; }
2321
+ .describe-bar { flex-shrink: 0; }
2322
+ .list { padding: 8px; flex: 1; min-height: 0; overflow-y: auto;
2323
+ scrollbar-width: thin; scrollbar-color: #30363d transparent; }
2324
+ .list::-webkit-scrollbar { width: 4px; }
2325
+ .list::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
2326
+ .empty { color: #484f58; text-align: center; padding: 32px; font-size: 13px; }
2327
+ .row {
2328
+ background: #0d1117; border-radius: 8px; margin-bottom: 6px;
2329
+ overflow: hidden; border: 1px solid #21262d;
2330
+ transition: border-color 0.15s;
2331
+ }
2332
+ .row:hover { border-color: #30363d; }
2333
+ .row.selected-row { border-color: rgba(47,129,247,0.5); }
2334
+ .row-header {
2335
+ display: flex; align-items: center; gap: 8px;
2336
+ padding: 10px 12px; cursor: pointer; user-select: none;
2337
+ }
2338
+ .row-header:hover { background: rgba(48,54,61,0.3); }
2339
+ .test-name { flex: 1; font-size: 13px; font-weight: 500; color: #e6edf3; }
2340
+ .test-date { font-size: 10.5px; color: #484f58; margin-right: 4px; }
2341
+ .test-tags { display: flex; gap: 4px; margin-right: 4px; }
2342
+ .test-tag {
2343
+ padding: 1px 7px; border-radius: 20px; font-size: 10px;
2344
+ background: rgba(47,129,247,0.1); color: #8b949e; border: 1px solid rgba(47,129,247,0.2);
2345
+ }
2346
+ .btn-icon {
2347
+ padding: 3px 8px; border: none; border-radius: 6px; cursor: pointer;
2348
+ font-size: 11px; background: #21262d; color: #8b949e;
2349
+ transition: background 0.15s, color 0.12s;
2350
+ }
2351
+ .btn-icon:hover { background: #30363d; color: #e6edf3; }
2352
+ .btn-del:hover { background: rgba(248,81,73,0.15); color: #f85149; }
2353
+ input[type="checkbox"] {
2354
+ width: 14px; height: 14px; accent-color: #2f81f7; cursor: pointer; flex-shrink: 0;
2355
+ }
2356
+ .row-body {
2357
+ background: #0d1117; padding: 10px 14px;
2358
+ border-top: 1px solid #21262d;
2359
+ }
2360
+ .section-title { font-size: 10px; color: #484f58; text-transform: uppercase;
2361
+ letter-spacing: 0.8px; margin-bottom: 6px; font-weight: 600; }
2362
+ .cmd-list { font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
2363
+ font-size: 11px; color: #c9d1d9; line-height: 1.8; }
2364
+ .icp-list { font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
2365
+ font-size: 11px; color: #3fb950; line-height: 1.8; margin-top: 10px; }
2366
+ .copy-row { display: flex; gap: 6px; margin-top: 10px; }
2367
+ `;
2368
+
2369
+ // src/components/test-editor/test-editor.template.ts
2370
+ function renderTestEditor(state, t) {
2371
+ const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest } = state;
2372
+ const tagFilterHtml = tags.length ? `<div class="tag-filter">
2373
+ ${tags.map((tag) => `<button class="tag-chip${activeTag === tag ? " active" : ""}" data-filter-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}
2374
+ </div>` : `<div class="tag-filter" style="color:#484f58;font-size:11px">${t("TEST_EDITOR.NO_TAGS")}</div>`;
2375
+ const describeBarHtml = selectMode && selectedVisible.length > 0 ? `<div class="describe-bar">
2376
+ <span class="selected-count">${selectedVisible.length} ${selectedVisible.length !== 1 ? t("TEST_EDITOR.SELECTED_PLURAL") : t("TEST_EDITOR.SELECTED_SINGULAR")}</span>
2377
+ <input id="describe-name" type="text" placeholder="${t("TEST_EDITOR.DESCRIBE_PLACEHOLDER")}" value="${escAttr(describeName)}" />
2378
+ <button class="btn-gen-describe" id="btn-gen-describe">${t("TEST_EDITOR.COPY_DESCRIBE")}</button>
2379
+ </div>` : "";
2380
+ const rows = visible.map((test, i) => {
2381
+ const expanded = expandedIndex === i;
2382
+ const date = new Date(test.createdAt).toLocaleString("es-ES", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
2383
+ const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
2384
+ const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
2385
+ const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
2386
+ const body = expanded ? `
2387
+ <div class="row-body">
2388
+ <div class="section-title">${t("TEST_EDITOR.SECTION_COMMANDS")} (${(test.commands ?? []).length})</div>
2389
+ <div class="cmd-list">${(test.commands ?? []).map(escHtml).join("<br>")}</div>
2390
+ ${icps.length ? `<div class="icp-list">
2391
+ <div class="section-title" style="margin-top:8px">${t("TEST_EDITOR.SECTION_INTERCEPTORS")}</div>
2392
+ ${icps.map(escHtml).join("<br>")}
2393
+ </div>` : ""}
2394
+ <div class="copy-row">
2395
+ <button class="btn-icon" data-action="copy-cmds" data-idx="${i}">${t("TEST_EDITOR.COPY_CMDS_BTN")}</button>
2396
+ ${icps.length ? `<button class="btn-icon" data-action="copy-icps" data-idx="${i}">${t("TEST_EDITOR.COPY_ICPS_BTN")}</button>` : ""}
2397
+ </div>
2398
+ </div>` : "";
2399
+ return `
2400
+ <div class="row${selectedIds.has(test.id) ? " selected-row" : ""}">
2401
+ <div class="row-header" data-action="expand" data-idx="${i}">
2402
+ ${checkbox}
2403
+ <span class="test-name">${escHtml(test.name)}</span>
2404
+ ${tagsHtml}
2405
+ <span class="test-date">${date}</span>
2406
+ <button class="btn-icon btn-del" data-action="delete" data-id="${test.id}" title="${t("TEST_EDITOR.DELETE_TITLE")}">\u{1F5D1}</button>
2407
+ </div>
2408
+ ${body}
2409
+ </div>`;
2410
+ }).join("");
2411
+ return `
2412
+ <div class="toolbar">
2413
+ ${tagFilterHtml}
2414
+ <button class="btn-select${selectMode ? " active" : ""}" id="btn-select-mode">
2415
+ ${selectMode ? t("TEST_EDITOR.CANCEL_SELECT") : t("TEST_EDITOR.MULTI_SELECT")}
2416
+ </button>
2417
+ </div>
2418
+ ${describeBarHtml}
2419
+ <div class="list">
2420
+ ${visible.length ? rows : `<div class="empty">${t("TEST_EDITOR.NO_TESTS")}</div>`}
2421
+ </div>`;
2422
+ }
2423
+
2424
+ // src/components/test-editor/test-editor.ts
2425
+ var TestEditorElement = class extends HTMLElement {
2426
+ shadow;
2427
+ persistence;
2428
+ translation = translationService;
2429
+ tests = [];
2430
+ expandedIndex = null;
2431
+ interceptorsByTest = {};
2432
+ activeTag = null;
2433
+ selectMode = false;
2434
+ selectedIds = /* @__PURE__ */ new Set();
2435
+ describeName = "";
2436
+ constructor() {
2437
+ super();
2438
+ this.shadow = this.attachShadow({ mode: "open" });
2439
+ }
2440
+ connectedCallback() {
2441
+ if (!this.persistence) this.persistence = new PersistenceService();
2442
+ this.loadTests();
2443
+ }
2444
+ async loadTests() {
2445
+ this.tests = await this.persistence.getAllTests();
2446
+ this.render();
2447
+ }
2448
+ async deleteTest(id) {
2449
+ await this.persistence.deleteTest(id);
2450
+ this.selectedIds.delete(id);
2451
+ await this.loadTests();
2452
+ }
2453
+ toggleExpand(index) {
2454
+ this.expandedIndex = this.expandedIndex === index ? null : index;
2455
+ if (this.expandedIndex !== null) {
2456
+ const test = this.tests[this.expandedIndex];
2457
+ if (test?.id && !this.interceptorsByTest[test.id]) {
2458
+ this.interceptorsByTest[test.id] = Array.isArray(test.interceptors) ? test.interceptors : [];
2459
+ }
2460
+ }
2461
+ this.render();
2462
+ }
2463
+ hasInterceptors(testId) {
2464
+ return Array.isArray(this.interceptorsByTest[testId]) && this.interceptorsByTest[testId].length > 0;
2465
+ }
2466
+ toggleSelectMode() {
2467
+ this.selectMode = !this.selectMode;
2468
+ if (!this.selectMode) this.selectedIds.clear();
2469
+ this.render();
2470
+ }
2471
+ toggleSelectTest(id) {
2472
+ if (this.selectedIds.has(id)) this.selectedIds.delete(id);
2473
+ else this.selectedIds.add(id);
2474
+ this.render();
2475
+ }
2476
+ generateDescribe() {
2477
+ const selected = this.tests.filter((t) => this.selectedIds.has(t.id));
2478
+ if (!selected.length) return;
2479
+ const name = this.describeName.trim() || this.t("TEST_EDITOR.DEFAULT_DESCRIBE");
2480
+ const allInterceptors = [...new Set(
2481
+ selected.flatMap((t) => this.interceptorsByTest[t.id] ?? t.interceptors ?? [])
2482
+ )];
2483
+ const beforeEach = allInterceptors.length ? ` beforeEach(() => {
2484
+ // Auto-generated Cypress interceptors
2485
+ ${allInterceptors.join("\n ")}
2486
+ });
2487
+
2488
+ ` : "";
2489
+ const itBlocks = selected.map((t) => {
2490
+ const cmds = (t.commands ?? []).map((c) => ` ${c}`).join("\n");
2491
+ return ` it('${(t.name ?? "").replace(/'/g, "\\'")}', () => {
2492
+ ${cmds}
2493
+ });`;
2494
+ }).join("\n\n");
2495
+ const block = `describe('${name.replace(/'/g, "\\'")}', () => {
2496
+ ${beforeEach}${itBlocks}
2497
+ });`;
2498
+ navigator.clipboard?.writeText(block);
2499
+ }
2500
+ copyToClipboard(text) {
2501
+ navigator.clipboard?.writeText(text);
2502
+ }
2503
+ get allTags() {
2504
+ const tagSet = /* @__PURE__ */ new Set();
2505
+ this.tests.forEach((t) => (t.tags ?? []).forEach((tag) => tagSet.add(tag)));
2506
+ return [...tagSet].sort();
2507
+ }
2508
+ get visibleTests() {
2509
+ const tag = this.activeTag;
2510
+ if (!tag) return this.tests;
2511
+ return this.tests.filter((t) => (t.tags ?? []).includes(tag));
2512
+ }
2513
+ t(key) {
2514
+ return this.translation.translate(key);
2515
+ }
2516
+ render() {
2517
+ const tags = this.allTags;
2518
+ const visible = this.visibleTests;
2519
+ const selectedVisible = visible.filter((t) => this.selectedIds.has(t.id));
2520
+ this.shadow.innerHTML = `<style>${TEST_EDITOR_STYLES}</style>${renderTestEditor({
2521
+ tags,
2522
+ visible,
2523
+ selectedVisible,
2524
+ activeTag: this.activeTag,
2525
+ selectMode: this.selectMode,
2526
+ selectedIds: this.selectedIds,
2527
+ describeName: this.describeName,
2528
+ expandedIndex: this.expandedIndex,
2529
+ interceptorsByTest: this.interceptorsByTest
2530
+ }, this.t.bind(this))}`;
2531
+ this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
2532
+ this.shadow.querySelectorAll("[data-filter-tag]").forEach((el) => {
2533
+ el.addEventListener("click", () => {
2534
+ const tag = el.dataset["filterTag"] ?? "";
2535
+ if (!tag) return;
2536
+ this.activeTag = this.activeTag === tag ? null : tag;
2537
+ this.render();
2538
+ });
2539
+ });
2540
+ const descInput = this.shadow.getElementById("describe-name");
2541
+ if (descInput) {
2542
+ descInput.addEventListener("input", () => {
2543
+ this.describeName = descInput.value;
2544
+ });
2545
+ }
2546
+ this.shadow.getElementById("btn-gen-describe")?.addEventListener("click", () => {
2547
+ this.describeName = descInput?.value ?? this.describeName;
2548
+ this.generateDescribe();
2549
+ });
2550
+ this.shadow.querySelectorAll("[data-select]").forEach((el) => {
2551
+ el.addEventListener("click", (ev) => {
2552
+ ev.stopPropagation();
2553
+ this.toggleSelectTest(Number(el.dataset["select"]));
2554
+ });
2555
+ });
2556
+ this.shadow.querySelectorAll('[data-action="expand"]').forEach((el) => {
2557
+ el.addEventListener("click", () => {
2558
+ if (this.selectMode) {
2559
+ const row = visible[Number(el.dataset["idx"])];
2560
+ if (row) this.toggleSelectTest(row.id);
2561
+ } else {
2562
+ this.toggleExpand(Number(el.dataset["idx"]));
2563
+ }
2564
+ });
2565
+ });
2566
+ this.shadow.querySelectorAll('[data-action="delete"]').forEach((el) => {
2567
+ el.addEventListener("click", (ev) => {
2568
+ ev.stopPropagation();
2569
+ this.deleteTest(Number(el.dataset["id"]));
2570
+ });
2571
+ });
2572
+ this.shadow.querySelectorAll('[data-action="copy-cmds"]').forEach((el) => {
2573
+ el.addEventListener("click", (ev) => {
2574
+ ev.stopPropagation();
2575
+ const idx = Number(el.dataset["idx"]);
2576
+ this.copyToClipboard((visible[idx]?.commands ?? []).join("\n"));
2577
+ });
2578
+ });
2579
+ this.shadow.querySelectorAll('[data-action="copy-icps"]').forEach((el) => {
2580
+ el.addEventListener("click", (ev) => {
2581
+ ev.stopPropagation();
2582
+ const idx = Number(el.dataset["idx"]);
2583
+ const t = visible[idx];
2584
+ this.copyToClipboard((this.interceptorsByTest[t?.id] ?? []).join("\n"));
2585
+ });
2586
+ });
2587
+ }
2588
+ };
2589
+ if (!customElements.get("test-editor")) {
2590
+ customElements.define("test-editor", TestEditorElement);
2591
+ }
2592
+
2593
+ // src/components/configuration/configuration.styles.ts
2594
+ var CONFIGURATION_STYLES = `
2595
+ :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
2596
+ * { box-sizing: border-box; }
2597
+
2598
+ /* \u2500\u2500 Grid container \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\u2500\u2500 */
2599
+ .cfg-grid {
2600
+ display: grid;
2601
+ grid-template-columns: 1fr 1fr;
2602
+ gap: 10px;
2603
+ padding: 14px;
2604
+ }
2605
+
2606
+ /* \u2500\u2500 Cards \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2607
+ .card {
2608
+ background: #161b22;
2609
+ border: 1px solid #21262d;
2610
+ border-radius: 10px;
2611
+ padding: 14px 16px;
2612
+ transition: border-color 0.15s;
2613
+ }
2614
+ .card:hover { border-color: #30363d; }
2615
+ .card-wide { grid-column: 1 / -1; }
2616
+
2617
+ /* \u2500\u2500 Card header \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\u2500\u2500\u2500\u2500\u2500 */
2618
+ .card-hd {
2619
+ display: flex; align-items: center; gap: 7px;
2620
+ font-size: 10px; font-weight: 700; text-transform: uppercase;
2621
+ letter-spacing: 0.8px; color: #484f58; margin-bottom: 12px;
2622
+ }
2623
+ .card-hd-icon { font-size: 13px; }
2624
+
2625
+ /* \u2500\u2500 Language \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2626
+ .field-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
2627
+ .field-label { font-size: 12px; color: #8b949e; }
2628
+ select {
2629
+ background: #0d1117; color: #e6edf3; border: 1px solid #30363d;
2630
+ border-radius: 6px; padding: 6px 10px; font-size: 12px; outline: none;
2631
+ cursor: pointer; transition: border-color 0.15s; flex-shrink: 0;
2632
+ }
2633
+ select:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.12); }
2634
+
2635
+ /* \u2500\u2500 HTTP toggle \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\u2500\u2500\u2500\u2500\u2500 */
2636
+ .check-row {
2637
+ display: flex; align-items: flex-start; gap: 10px;
2638
+ cursor: pointer; user-select: none;
2639
+ }
2640
+ input[type="checkbox"] { width: 15px; height: 15px; margin-top: 2px; cursor: pointer; accent-color: #2f81f7; flex-shrink: 0; }
2641
+ .check-title { font-size: 12px; color: #c9d1d9; margin-bottom: 3px; }
2642
+ .check-sub { font-size: 10px; color: #484f58; line-height: 1.5; }
2643
+
2644
+ /* \u2500\u2500 Cypress folder \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\u2500\u2500 */
2645
+ .fs-layout { display: flex; gap: 12px; align-items: flex-start; }
2646
+ .fs-tree {
2647
+ flex-shrink: 0;
2648
+ margin: 0; padding: 8px 10px;
2649
+ background: #0d1117; border: 1px solid #21262d; border-radius: 6px;
2650
+ font-size: 10px; color: #c9d1d9; line-height: 1.8;
2651
+ font-family: 'Cascadia Code','Fira Code','Consolas',monospace;
2652
+ }
2653
+ .fs-right { display: flex; flex-direction: column; gap: 10px; flex: 1; }
2654
+ .fs-status {
2655
+ display: flex; align-items: center; gap: 8px;
2656
+ font-size: 12px; color: #8b949e;
2657
+ background: #0d1117; border: 1px solid #21262d; border-radius: 6px;
2658
+ padding: 8px 10px;
2659
+ }
2660
+ .fs-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
2661
+ .fs-dot.on { background: #3fb950; box-shadow: 0 0 6px rgba(63,185,80,.5); }
2662
+ .fs-dot.off { background: #484f58; }
2663
+ .fs-folder { color: #e6edf3; font-family: 'Cascadia Code','Fira Code','Consolas',monospace; font-size: 11px; }
2664
+
2665
+ /* \u2500\u2500 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2666
+ .btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
2667
+ button {
2668
+ padding: 7px 14px; border: 1px solid #30363d; border-radius: 6px; cursor: pointer;
2669
+ font-size: 12px; font-weight: 500; background: #21262d; color: #8b949e;
2670
+ transition: background 0.15s, color 0.12s, border-color 0.15s;
2671
+ }
2672
+ button:hover { background: #30363d; color: #e6edf3; border-color: #484f58; }
2673
+ .btn-import {
2674
+ display: inline-block;
2675
+ padding: 7px 14px; border: 1px solid #30363d; border-radius: 6px; cursor: pointer;
2676
+ font-size: 12px; font-weight: 500; background: #21262d; color: #8b949e;
2677
+ transition: background 0.15s, color 0.12s, border-color 0.15s;
2678
+ }
2679
+ .btn-import:hover { background: #30363d; color: #e6edf3; border-color: #484f58; }
2680
+ .btn-danger { border-color: rgba(248,81,73,.4); color: #f85149; background: transparent; }
2681
+ .btn-danger:hover { background: rgba(248,81,73,.08); border-color: #f85149; color: #f85149; }
2682
+ .file-input { display: none; }
2683
+
2684
+ /* \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 */
2685
+ .data-desc { font-size: 11px; color: #484f58; margin-bottom: 10px; line-height: 1.5; }
2686
+ `;
2687
+
2688
+ // src/components/configuration/configuration.template.ts
2689
+ var LANGS = [
2690
+ { value: "es", label: "Espa\xF1ol" },
2691
+ { value: "en", label: "English" },
2692
+ { value: "fr", label: "Fran\xE7ais" },
2693
+ { value: "it", label: "Italiano" },
2694
+ { value: "de", label: "Deutsch" }
2695
+ ];
2696
+ function renderConfiguration(state, t) {
2697
+ const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled } = state;
2698
+ const langOptions = LANGS.map(
2699
+ (l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
2700
+ ).join("");
2701
+ return `
2702
+ <div class="cfg-grid">
2703
+
2704
+ <!-- Language -->
2705
+ <div class="card">
2706
+ <div class="card-hd">${t("CONFIG.LANG_SECTION")}</div>
2707
+ <div class="field-row">
2708
+ <span class="field-label">${t("CONFIG.LANG_FIELD")}</span>
2709
+ <select id="lang-select">${langOptions}</select>
2710
+ </div>
2711
+ </div>
2712
+
2713
+ <!-- HTTP Advanced -->
2714
+ <div class="card">
2715
+ <div class="card-hd">${t("CONFIG.HTTP_SECTION")}</div>
2716
+ <label class="check-row">
2717
+ <input type="checkbox" id="http-toggle" ${advancedHttpConfig ? "checked" : ""} />
2718
+ <div>
2719
+ <div class="check-title">${t("CONFIG.HTTP_TITLE")}</div>
2720
+ <div class="check-sub">${t("CONFIG.HTTP_SUB")}</div>
2721
+ </div>
2722
+ </label>
2723
+ </div>
2724
+
2725
+ <!-- Smart Selector -->
2726
+ <div class="card">
2727
+ <div class="card-hd">${t("CONFIG.SMART_SELECTOR_SECTION")}</div>
2728
+ <label class="check-row">
2729
+ <input type="checkbox" id="smart-selector-toggle" ${smartSelectorEnabled ? "checked" : ""} />
2730
+ <div>
2731
+ <div class="check-title">${t("CONFIG.SMART_SELECTOR_TITLE")}</div>
2732
+ <div class="check-sub">${t("CONFIG.SMART_SELECTOR_SUB")}</div>
2733
+ </div>
2734
+ </label>
2735
+ </div>
2736
+
2737
+ <!-- Selector Strategy -->
2738
+ <div class="card card-wide">
2739
+ <div class="card-hd">${t("CONFIG.SELECTOR_SECTION")}</div>
2740
+ <div class="field-row">
2741
+ <span class="field-label">${t("CONFIG.SELECTOR_LABEL")}</span>
2742
+ <select id="selector-strategy">
2743
+ <option value="data-cy" ${selectorStrategy === "data-cy" ? "selected" : ""}>${t("CONFIG.SELECTOR_OPT_DATACY")}</option>
2744
+ <option value="data-testid" ${selectorStrategy === "data-testid" ? "selected" : ""}>${t("CONFIG.SELECTOR_OPT_TESTID")}</option>
2745
+ <option value="aria-label" ${selectorStrategy === "aria-label" ? "selected" : ""}>${t("CONFIG.SELECTOR_OPT_ARIA")}</option>
2746
+ <option value="id" ${selectorStrategy === "id" ? "selected" : ""}>${t("CONFIG.SELECTOR_OPT_ID")}</option>
2747
+ </select>
2748
+ </div>
2749
+ <div class="check-sub" style="margin-top:8px">${t("CONFIG.SELECTOR_HINT")}</div>
2750
+ </div>
2751
+
2752
+ <!-- Cypress Folder -->
2753
+ <div class="card card-wide">
2754
+ <div class="card-hd">${t("CONFIG.FOLDER_SECTION")}</div>
2755
+ <div class="fs-layout">
2756
+ <pre class="fs-tree">cypress/ <span style="color:#484f58">\u2190 selecciona</span>
2757
+ \u2514\u2500\u2500 e2e/
2758
+ \u2514\u2500\u2500 *.cy.ts</pre>
2759
+ <div class="fs-right">
2760
+ <div class="fs-status">
2761
+ <span class="fs-dot ${filesystemGranted ? "on" : "off"}"></span>
2762
+ ${filesystemGranted && cypressFolderName ? `<span>${t("CONFIG.FOLDER_CONNECTED")}</span>&nbsp;<span class="fs-folder">\u{1F4C1} ${cypressFolderName}</span>` : `<span>${t("CONFIG.FOLDER_NOT_SET")}</span>`}
2763
+ </div>
2764
+ <div class="btn-row">
2765
+ <button id="btn-change-folder">
2766
+ ${filesystemGranted ? t("CONFIG.FOLDER_CHANGE_BTN") : t("CONFIG.FOLDER_SELECT_BTN")}
2767
+ </button>
2768
+ ${filesystemGranted ? `<button id="btn-revoke" class="btn-danger">${t("CONFIG.FOLDER_REVOKE_BTN")}</button>` : ""}
2769
+ </div>
2770
+ </div>
2771
+ </div>
2772
+ </div>
2773
+
2774
+ <!-- Data -->
2775
+ <div class="card card-wide">
2776
+ <div class="card-hd">${t("CONFIG.DATA_SECTION")}</div>
2777
+ <p class="data-desc">${t("CONFIG.DATA_DESC")}</p>
2778
+ <div class="btn-row">
2779
+ <button id="btn-export">${t("CONFIG.EXPORT_BTN")}</button>
2780
+ <label style="cursor:pointer;margin:0">
2781
+ <span class="btn-import">${t("CONFIG.IMPORT_BTN")}</span>
2782
+ <input type="file" class="file-input" id="file-input" accept=".json" />
2783
+ </label>
2784
+ </div>
2785
+ </div>
2786
+
2787
+ </div>`;
2788
+ }
2789
+
2790
+ // src/components/configuration/configuration.ts
2791
+ var ConfigurationElement = class extends HTMLElement {
2792
+ shadow;
2793
+ persistence;
2794
+ translation;
2795
+ selectedLanguage = "es";
2796
+ advancedHttpConfig = false;
2797
+ selectorStrategy = "data-cy";
2798
+ smartSelectorEnabled = true;
2799
+ filesystemGranted = false;
2800
+ cypressFolderName = null;
2801
+ constructor() {
2802
+ super();
2803
+ this.shadow = this.attachShadow({ mode: "open" });
2804
+ this.advancedHttpConfig = localStorage.getItem("extendedHttpCommands") === "true";
2805
+ }
2806
+ connectedCallback() {
2807
+ if (!this.persistence) this.persistence = new PersistenceService();
2808
+ if (!this.translation) this.translation = new TranslationService();
2809
+ this.loadConfig();
2810
+ this.render();
2811
+ }
2812
+ t(key) {
2813
+ return this.translation.translate(key);
2814
+ }
2815
+ async loadConfig() {
2816
+ const config = await this.persistence.getGeneralConfig();
2817
+ if (config?.["language"]) {
2818
+ this.selectedLanguage = config["language"];
2819
+ this.translation.setLang(this.selectedLanguage);
2820
+ }
2821
+ this.advancedHttpConfig = localStorage.getItem("extendedHttpCommands") === "true";
2822
+ this.selectorStrategy = config?.["selectorStrategy"] ?? "data-cy";
2823
+ this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
2824
+ this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
2825
+ const handle = config?.["cypressDirectoryHandle"];
2826
+ this.cypressFolderName = handle?.name ?? null;
2827
+ this.render();
2828
+ }
2829
+ async onLanguageChange(lang) {
2830
+ this.selectedLanguage = lang;
2831
+ this.translation.setLang(lang);
2832
+ await this.persistence.setConfig({ language: lang });
2833
+ this.render();
2834
+ }
2835
+ onAdvancedHttpConfigChange(checked) {
2836
+ this.advancedHttpConfig = checked;
2837
+ localStorage.setItem("extendedHttpCommands", checked ? "true" : "false");
2838
+ this.persistence.setConfig({ extendedHttpCommands: checked ? "true" : "false" });
2839
+ this.render();
2840
+ }
2841
+ async onSmartSelectorChange(enabled) {
2842
+ this.smartSelectorEnabled = enabled;
2843
+ await this.persistence.setConfig({ smartSelectorEnabled: enabled ? "true" : "false" });
2844
+ this.dispatchEvent(new CustomEvent("smartselectorchange", { detail: enabled, bubbles: true, composed: true }));
2845
+ this.render();
2846
+ }
2847
+ async onSelectorStrategyChange(strategy) {
2848
+ this.selectorStrategy = strategy;
2849
+ await this.persistence.setConfig({ selectorStrategy: strategy });
2850
+ this.dispatchEvent(new CustomEvent("selectorstrategychange", { detail: strategy, bubbles: true, composed: true }));
2851
+ this.render();
2852
+ }
2853
+ async changeFolder() {
2854
+ try {
2855
+ await this.persistence.requestDirectoryPermissions();
2856
+ await this.loadConfig();
2857
+ showToast(this.t("CONFIG.FOLDER_UPDATED_TOAST"));
2858
+ } catch (e) {
2859
+ if (e?.name !== "AbortError") {
2860
+ showToast(this.t("CONFIG.FOLDER_ERROR_TOAST"), false);
2861
+ }
2862
+ }
2863
+ }
2864
+ async revokeAccess() {
2865
+ await this.persistence.setConfig({ allowReadWriteFiles: "false", cypressDirectoryHandle: null });
2866
+ this.filesystemGranted = false;
2867
+ this.cypressFolderName = null;
2868
+ this.render();
2869
+ }
2870
+ async exportAllData() {
2871
+ const tests = await this.persistence.getAllTests();
2872
+ const blob = new Blob([JSON.stringify({ tests, interceptors: [] }, null, 2)], { type: "application/json" });
2873
+ const url = URL.createObjectURL(blob);
2874
+ const a = document.createElement("a");
2875
+ a.href = url;
2876
+ a.download = "e2e-cypress-export.json";
2877
+ a.click();
2878
+ URL.revokeObjectURL(url);
2879
+ }
2880
+ async importAllData(file) {
2881
+ const text = await file.text();
2882
+ let data;
2883
+ try {
2884
+ data = JSON.parse(text);
2885
+ } catch {
2886
+ throw new Error(this.t("CONFIG.JSON_INVALID"));
2887
+ }
2888
+ if (!data || !Array.isArray(data.tests) || !Array.isArray(data.interceptors)) {
2889
+ throw new Error(this.t("CONFIG.JSON_BAD_FORMAT"));
2890
+ }
2891
+ await this.persistence.clearAllData();
2892
+ await this.persistence.ingestFileData(data.tests, data.interceptors);
2893
+ }
2894
+ render() {
2895
+ this.shadow.innerHTML = `<style>${CONFIGURATION_STYLES}</style>${renderConfiguration({
2896
+ selectedLanguage: this.selectedLanguage,
2897
+ advancedHttpConfig: this.advancedHttpConfig,
2898
+ selectorStrategy: this.selectorStrategy,
2899
+ filesystemGranted: this.filesystemGranted,
2900
+ cypressFolderName: this.cypressFolderName,
2901
+ smartSelectorEnabled: this.smartSelectorEnabled
2902
+ }, this.t.bind(this))}`;
2903
+ ;
2904
+ this.shadow.getElementById("lang-select").addEventListener(
2905
+ "change",
2906
+ (e) => this.onLanguageChange(e.target.value)
2907
+ );
2908
+ this.shadow.getElementById("http-toggle").addEventListener(
2909
+ "change",
2910
+ (e) => this.onAdvancedHttpConfigChange(e.target.checked)
2911
+ );
2912
+ this.shadow.getElementById("smart-selector-toggle").addEventListener(
2913
+ "change",
2914
+ (e) => this.onSmartSelectorChange(e.target.checked)
2915
+ );
2916
+ this.shadow.getElementById("selector-strategy").addEventListener(
2917
+ "change",
2918
+ (e) => this.onSelectorStrategyChange(e.target.value)
2919
+ );
2920
+ this.shadow.getElementById("btn-change-folder")?.addEventListener("click", () => this.changeFolder());
2921
+ this.shadow.getElementById("btn-revoke")?.addEventListener("click", () => this.revokeAccess());
2922
+ this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.exportAllData());
2923
+ this.shadow.getElementById("file-input").addEventListener("change", async (e) => {
2924
+ const file = e.target.files?.[0];
2925
+ if (!file) return;
2926
+ try {
2927
+ await this.importAllData(file);
2928
+ alert(this.t("CONFIG.IMPORT_SUCCESS"));
2929
+ } catch (err) {
2930
+ alert(err.message ?? this.t("CONFIG.IMPORT_ERROR"));
2931
+ }
2932
+ const target = e.target;
2933
+ if (target) target.value = "";
2934
+ });
2935
+ }
2936
+ };
2937
+ if (!customElements.get("e2e-configuration")) {
2938
+ customElements.define("e2e-configuration", ConfigurationElement);
2939
+ }
2940
+
2941
+ // src/components/advanced-test-editor/advanced-test-editor.styles.ts
2942
+ var ADVANCED_TEST_EDITOR_STYLES = `
2943
+ :host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
2944
+ * { box-sizing: border-box; }
2945
+ .layout { display: flex; flex: 1; min-height: 0; }
2946
+ .sidebar {
2947
+ width: 220px; border-right: 1px solid #21262d; overflow-y: auto;
2948
+ padding: 8px; background: #0d1117; flex-shrink: 0;
2949
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
2950
+ }
2951
+ .sidebar::-webkit-scrollbar { width: 4px; }
2952
+ .sidebar::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
2953
+ .main { flex: 1; display: flex; flex-direction: column; overflow: hidden; background: #161b22; }
2954
+ .no-perm {
2955
+ padding: 28px; color: #484f58; font-size: 13px; text-align: center;
2956
+ display: flex; flex-direction: column; align-items: center; gap: 12px; line-height: 1.6;
2957
+ }
2958
+ .tree-item {
2959
+ padding: 5px 8px; border-radius: 5px; cursor: pointer;
2960
+ font-size: 12px; color: #8b949e; white-space: nowrap;
2961
+ overflow: hidden; text-overflow: ellipsis;
2962
+ transition: background 0.12s, color 0.12s;
2963
+ }
2964
+ .tree-item:hover { background: #161b22; color: #c9d1d9; }
2965
+ .tree-item.selected { background: rgba(47,129,247,0.12); color: #2f81f7; }
2966
+ .tree-item.dir { color: #e3b341; font-weight: 600; }
2967
+ .content-area {
2968
+ flex: 1; padding: 12px 14px; overflow-y: auto;
2969
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
2970
+ }
2971
+ .content-area::-webkit-scrollbar { width: 4px; }
2972
+ .content-area::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
2973
+ .file-name { font-size: 11px; color: #484f58; margin-bottom: 8px; font-family: monospace; }
2974
+ pre {
2975
+ background: #0d1117; padding: 12px; border-radius: 8px;
2976
+ font-size: 11px; color: #c9d1d9; overflow-x: auto;
2977
+ white-space: pre-wrap; word-break: break-all; margin: 0;
2978
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
2979
+ line-height: 1.7; overflow-y: auto;
2980
+ border: 1px solid #21262d;
2981
+ }
2982
+ .footer {
2983
+ padding: 10px 14px; border-top: 1px solid #21262d;
2984
+ display: flex; gap: 8px; justify-content: flex-end; background: #161b22;
2985
+ }
2986
+ button {
2987
+ padding: 6px 16px; border: 1px solid #30363d; border-radius: 6px; cursor: pointer;
2988
+ font-size: 12px; font-weight: 500; background: #21262d; color: #8b949e;
2989
+ transition: background 0.15s, color 0.12s;
2990
+ }
2991
+ button:hover { background: #30363d; color: #e6edf3; }
2992
+ button:disabled { opacity: 0.35; cursor: default; }
2993
+ .btn-save { background: #2f81f7; border-color: #2f81f7; color: #fff; }
2994
+ .btn-save:hover { background: #1f6feb; border-color: #1f6feb; color: #fff; }
2995
+ .btn-save:disabled { background: #21262d; border-color: #30363d; color: #8b949e; }
2996
+ .placeholder { color: #484f58; font-size: 13px; padding: 28px; text-align: center; }
2997
+ .block-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
2998
+ .btn-copy {
2999
+ padding: 3px 10px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
3000
+ font-size: 10px; font-weight: 500; background: #21262d; color: #8b949e;
3001
+ transition: background 0.12s, color 0.12s; white-space: nowrap; flex-shrink: 0;
3002
+ }
3003
+ .btn-copy:hover { background: #30363d; color: #e6edf3; }
3004
+ `;
3005
+
3006
+ // src/components/advanced-test-editor/advanced-test-editor.template.ts
3007
+ function renderNoPermission(needsReauth, t) {
3008
+ const reauthBtn = needsReauth ? `<button id="btn-reauth"
3009
+ style="margin-top:14px;padding:7px 16px;border:none;border-radius:6px;cursor:pointer;
3010
+ font-size:12px;font-weight:500;background:#2f81f7;color:#fff">
3011
+ ${t("ADVANCED_EDITOR.REAUTH_BTN")}
3012
+ </button>` : "";
3013
+ return `
3014
+ <div class="no-perm">
3015
+ <div>${needsReauth ? t("ADVANCED_EDITOR.PERMISSION_EXPIRED") : t("ADVANCED_EDITOR.NO_ACCESS")}</div>
3016
+ <div style="font-size:11px">${needsReauth ? t("ADVANCED_EDITOR.PERMISSION_EXPIRED_HINT") : t("ADVANCED_EDITOR.NO_ACCESS_HINT")}</div>
3017
+ ${reauthBtn}
3018
+ </div>`;
3019
+ }
3020
+ function renderAdvancedEditor(state, t) {
3021
+ const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, saveButtonEnabled } = state;
3022
+ const treeHtml = e2eTree.length ? renderTree(e2eTree, selectedFile) : `<div class="tree-item" style="color:#6c7a99">${t("ADVANCED_EDITOR.NO_FILES")}</div>`;
3023
+ const contentHtml = selectedFileContent ? `<div class="file-name">\u{1F4C4} ${escHtml(selectedFile?.name ?? "")}</div>
3024
+ <pre>${escHtml(selectedFileContent.slice(0, 4e3))}${selectedFileContent.length > 4e3 ? "\n..." : ""}</pre>` : `<div class="placeholder">${t("ADVANCED_EDITOR.SELECT_FILE")}</div>`;
3025
+ const itHtml = testItBlock ? `<div style="margin-top:10px">
3026
+ <div class="file-name block-header">
3027
+ <span>${t("ADVANCED_EDITOR.IT_LABEL")}</span>
3028
+ <button id="btn-copy-it" class="btn-copy">${t("ADVANCED_EDITOR.COPY_IT_BTN")}</button>
3029
+ </div>
3030
+ <pre style="max-height:120px;font-size:10.5px">${escHtml(testItBlock.slice(0, 500))}</pre>
3031
+ </div>` : "";
3032
+ const interceptorsHtml = interceptorsBlock ? `<div style="margin-top:10px">
3033
+ <div class="file-name block-header">
3034
+ <span>${t("ADVANCED_EDITOR.BEFORE_EACH_LABEL")}</span>
3035
+ <button id="btn-copy-interceptors" class="btn-copy">${t("ADVANCED_EDITOR.COPY_ICP_BTN")}</button>
3036
+ </div>
3037
+ <pre style="max-height:120px;font-size:10.5px;color:#3fb950">${escHtml(interceptorsBlock.slice(0, 500))}</pre>
3038
+ </div>` : "";
3039
+ return `
3040
+ <div class="layout">
3041
+ <div class="sidebar">${treeHtml}</div>
3042
+ <div class="main">
3043
+ <div class="content-area">${contentHtml}${itHtml}${interceptorsHtml}</div>
3044
+ <div class="footer">
3045
+ <button id="btn-save" class="btn-save"
3046
+ ${!saveButtonEnabled || !testItBlock ? "disabled" : ""}>
3047
+ ${t("ADVANCED_EDITOR.INSERT_BTN")}
3048
+ </button>
3049
+ <button id="btn-edit" ${!saveButtonEnabled ? "disabled" : ""}>
3050
+ ${t("ADVANCED_EDITOR.EDIT_MANUAL_BTN")}
3051
+ </button>
3052
+ <button id="btn-close">${t("ADVANCED_EDITOR.CLOSE_BTN")}</button>
3053
+ </div>
3054
+ </div>
3055
+ </div>`;
3056
+ }
3057
+ function renderTree(nodes, selected, indent = 0) {
3058
+ return nodes.map((n) => {
3059
+ const isFile = n.kind === "file";
3060
+ const isSel = selected === n || selected?.name === n.name;
3061
+ const cls = `tree-item${isFile ? "" : " dir"}${isSel ? " selected" : ""}`;
3062
+ const pad = `padding-left:${8 + indent * 14}px`;
3063
+ if (!isFile && n.children?.length) {
3064
+ return `<div class="${cls}" style="${pad}">\u{1F4C1} ${escHtml(n.name)}</div>
3065
+ ${renderTree(n.children, selected, indent + 1)}`;
3066
+ }
3067
+ const data = JSON.stringify({ kind: n.kind, name: n.name }).replace(/"/g, "&quot;");
3068
+ return `<div class="${cls}" style="${pad}" data-file="${data}">\u{1F4C4} ${escHtml(n.name)}</div>`;
3069
+ }).join("");
3070
+ }
3071
+ async function findFileHandleRecursive(dir, name) {
3072
+ for await (const entry of dir.values()) {
3073
+ if (entry.kind === "file" && entry.name === name) return entry;
3074
+ if (entry.kind === "directory") {
3075
+ const found = await findFileHandleRecursive(entry, name);
3076
+ if (found) return found;
3077
+ }
3078
+ }
3079
+ return null;
3080
+ }
3081
+
3082
+ // src/components/advanced-test-editor/advanced-test-editor.ts
3083
+ var AdvancedTestEditorElement = class extends HTMLElement {
3084
+ shadow;
3085
+ transformationService = new AdvancedTestTransformationService();
3086
+ persistence;
3087
+ translation;
3088
+ testId;
3089
+ e2eTree = [];
3090
+ selectedFile = null;
3091
+ saveButtonEnabled = false;
3092
+ selectedFileHandle = null;
3093
+ selectedFileContent = null;
3094
+ testItBlock = "";
3095
+ interceptorsBlock = "";
3096
+ isPreviewMode = false;
3097
+ previewFileName = null;
3098
+ previewFileContent = null;
3099
+ hasPermission = false;
3100
+ needsReauth = false;
3101
+ _dirHandle = null;
3102
+ constructor() {
3103
+ super();
3104
+ this.shadow = this.attachShadow({ mode: "open" });
3105
+ }
3106
+ connectedCallback() {
3107
+ if (!this.persistence) this.persistence = new PersistenceService();
3108
+ if (!this.translation) this.translation = new TranslationService();
3109
+ this.init();
3110
+ }
3111
+ t(key) {
3112
+ return this.translation.translate(key);
3113
+ }
3114
+ async init() {
3115
+ const config = await this.persistence.getGeneralConfig();
3116
+ const allowed = config?.["allowReadWriteFiles"] === "true";
3117
+ if (!allowed) {
3118
+ this.hasPermission = false;
3119
+ if (this.testId !== void 0) await this.loadCypressCommandsForTest(this.testId);
3120
+ this.render();
3121
+ return;
3122
+ }
3123
+ const handle = config?.["cypressDirectoryHandle"];
3124
+ if (!handle) {
3125
+ this.hasPermission = false;
3126
+ if (this.testId !== void 0) await this.loadCypressCommandsForTest(this.testId);
3127
+ this.render();
3128
+ return;
3129
+ }
3130
+ const perm = await handle.queryPermission({ mode: "readwrite" });
3131
+ this._dirHandle = handle;
3132
+ this.hasPermission = perm === "granted";
3133
+ this.needsReauth = perm === "prompt";
3134
+ if (this.testId !== void 0) await this.loadCypressCommandsForTest(this.testId);
3135
+ if (this.hasPermission) await this.getFoldersData();
3136
+ this.render();
3137
+ }
3138
+ async getFoldersData() {
3139
+ if (!this.hasPermission || !this._dirHandle) return;
3140
+ try {
3141
+ for await (const entry of this._dirHandle.values()) {
3142
+ if (entry.kind === "directory" && entry.name === "e2e") {
3143
+ const tree = await this.transformationService.scanDirectory(entry);
3144
+ this.e2eTree = tree.children ?? [];
3145
+ this.render();
3146
+ return;
3147
+ }
3148
+ }
3149
+ } catch {
3150
+ }
3151
+ }
3152
+ async saveCommandsToFile() {
3153
+ if (!this.selectedFileHandle || !this.selectedFileContent || !this.testItBlock) return;
3154
+ let content = this.selectedFileContent;
3155
+ if (this.interceptorsBlock) {
3156
+ content = this.transformationService.insertBeforeEach(content, this.interceptorsBlock);
3157
+ }
3158
+ content = this.transformationService.insertItBlock(content, this.testItBlock);
3159
+ if (!content) return;
3160
+ const writable = await this.selectedFileHandle.createWritable();
3161
+ await writable.write(content);
3162
+ await writable.close();
3163
+ this.dispatchEvent(new CustomEvent("closemodal", { bubbles: true, composed: true }));
3164
+ }
3165
+ async onFileClick(file) {
3166
+ if (!this.transformationService.isFile(file)) return;
3167
+ this.selectedFile = file;
3168
+ this.saveButtonEnabled = true;
3169
+ if (!this._dirHandle) return;
3170
+ const handle = await findFileHandleRecursive(this._dirHandle, file.name);
3171
+ if (!handle) return;
3172
+ this.selectedFileHandle = handle;
3173
+ const fileObj = await handle.getFile();
3174
+ this.selectedFileContent = await fileObj.text();
3175
+ this.render();
3176
+ }
3177
+ markFileAsSelected(file) {
3178
+ this.selectedFile = file;
3179
+ }
3180
+ async loadCypressCommandsForTest(testId) {
3181
+ const test = await this.persistence.getTestById(testId);
3182
+ if (test) {
3183
+ this.testItBlock = test.itBlock ?? "";
3184
+ this.interceptorsBlock = test.interceptorsBlock ?? "";
3185
+ } else {
3186
+ this.testItBlock = "";
3187
+ this.interceptorsBlock = "";
3188
+ }
3189
+ this.render();
3190
+ }
3191
+ copyToClipboard(text) {
3192
+ navigator.clipboard?.writeText(text);
3193
+ }
3194
+ openEditManually() {
3195
+ if (!this.selectedFileHandle || !this.selectedFileContent) return;
3196
+ this.dispatchEvent(new CustomEvent("openfileeditor", {
3197
+ detail: {
3198
+ handle: this.selectedFileHandle,
3199
+ content: this.selectedFileContent,
3200
+ fileName: this.selectedFile?.name ?? "",
3201
+ testId: this.testId,
3202
+ itBlock: this.testItBlock,
3203
+ interceptorsBlock: this.interceptorsBlock
3204
+ },
3205
+ bubbles: true,
3206
+ composed: true
3207
+ }));
3208
+ }
3209
+ closePreview() {
3210
+ this.isPreviewMode = false;
3211
+ this.previewFileName = null;
3212
+ this.previewFileContent = null;
3213
+ if (this.testId !== void 0) {
3214
+ this.dispatchEvent(new CustomEvent("closemodal", { bubbles: true, composed: true }));
3215
+ }
3216
+ this.render();
3217
+ }
3218
+ render() {
3219
+ if (!this.hasPermission) {
3220
+ this.shadow.innerHTML = `<style>${ADVANCED_TEST_EDITOR_STYLES}</style>${renderNoPermission(this.needsReauth, this.t.bind(this))}`;
3221
+ this.shadow.getElementById("btn-reauth")?.addEventListener("click", async () => {
3222
+ if (!this._dirHandle) return;
3223
+ const perm = await this._dirHandle.requestPermission({ mode: "readwrite" });
3224
+ if (perm === "granted") {
3225
+ this.hasPermission = true;
3226
+ this.needsReauth = false;
3227
+ await this.getFoldersData();
3228
+ this.render();
3229
+ }
3230
+ });
3231
+ return;
3232
+ }
3233
+ this.shadow.innerHTML = `<style>${ADVANCED_TEST_EDITOR_STYLES}</style>${renderAdvancedEditor({
3234
+ e2eTree: this.e2eTree,
3235
+ selectedFile: this.selectedFile,
3236
+ selectedFileContent: this.selectedFileContent,
3237
+ testItBlock: this.testItBlock,
3238
+ interceptorsBlock: this.interceptorsBlock,
3239
+ saveButtonEnabled: this.saveButtonEnabled
3240
+ }, this.t.bind(this))}`;
3241
+ this.shadow.querySelectorAll("[data-file]").forEach((el) => {
3242
+ el.addEventListener("click", () => {
3243
+ const data = el.dataset["file"];
3244
+ if (data) this.onFileClick(JSON.parse(data));
3245
+ });
3246
+ });
3247
+ this.shadow.getElementById("btn-save")?.addEventListener("click", () => this.saveCommandsToFile());
3248
+ this.shadow.getElementById("btn-edit")?.addEventListener("click", () => this.openEditManually());
3249
+ this.shadow.getElementById("btn-copy-it")?.addEventListener("click", () => this.copyToClipboard(this.testItBlock));
3250
+ this.shadow.getElementById("btn-copy-interceptors")?.addEventListener("click", () => this.copyToClipboard(this.interceptorsBlock));
3251
+ this.shadow.getElementById("btn-close")?.addEventListener("click", () => this.closePreview());
3252
+ }
3253
+ };
3254
+ if (!customElements.get("advanced-test-editor")) {
3255
+ customElements.define("advanced-test-editor", AdvancedTestEditorElement);
3256
+ }
3257
+
3258
+ // src/components/file-preview/file-preview.styles.ts
3259
+ var FILE_PREVIEW_STYLES = `
3260
+ :host { display: block; height: 100%; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
3261
+ * { box-sizing: border-box; }
3262
+ .container { display: flex; flex-direction: column; height: 100%; }
3263
+ .header {
3264
+ display: flex; align-items: center; gap: 8px;
3265
+ padding: 8px 12px; border-bottom: 1px solid #21262d;
3266
+ background: #161b22;
3267
+ }
3268
+ .file-name { flex: 1; font-size: 12px; color: #8b949e;
3269
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; }
3270
+ .body { display: flex; flex: 1; overflow: hidden; min-height: 0; }
3271
+ .editor {
3272
+ flex: 1; min-width: 0; padding: 14px;
3273
+ background: #0d1117; color: #c9d1d9; border: none; outline: none;
3274
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; font-size: 12px;
3275
+ line-height: 1.7; resize: none;
3276
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
3277
+ }
3278
+ .editor::-webkit-scrollbar { width: 5px; }
3279
+ .editor::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
3280
+ .blocks-panel {
3281
+ width: 260px; flex-shrink: 0; border-left: 1px solid #21262d;
3282
+ background: #0d1117; padding: 10px 12px; overflow-y: auto;
3283
+ display: flex; flex-direction: column; gap: 10px;
3284
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
3285
+ }
3286
+ .blocks-panel::-webkit-scrollbar { width: 4px; }
3287
+ .blocks-panel::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
3288
+ .block-section { display: flex; flex-direction: column; gap: 4px; }
3289
+ .block-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; }
3290
+ .block-label { font-size: 10px; color: #484f58; text-transform: uppercase; letter-spacing: 0.6px; font-weight: 600; }
3291
+ .btn-copy-sm {
3292
+ padding: 2px 8px; border: 1px solid #30363d; border-radius: 4px; cursor: pointer;
3293
+ font-size: 10px; font-weight: 500; background: #21262d; color: #8b949e;
3294
+ transition: background 0.12s, color 0.12s; white-space: nowrap;
3295
+ }
3296
+ .btn-copy-sm:hover { background: #30363d; color: #e6edf3; }
3297
+ .block-pre {
3298
+ margin: 0; padding: 6px 8px; background: #161b22;
3299
+ border-radius: 5px; border: 1px solid #21262d;
3300
+ font-size: 10px; color: #c9d1d9; line-height: 1.6;
3301
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
3302
+ max-height: 280px; overflow-y: auto; white-space: pre-wrap; word-break: break-all;
3303
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
3304
+ }
3305
+ .diff-panel {
3306
+ flex: 1; overflow-y: auto; background: #0d1117; border-left: 1px solid #21262d;
3307
+ padding: 8px 0; min-width: 0;
3308
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; font-size: 11px;
3309
+ line-height: 1.6;
3310
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
3311
+ }
3312
+ .diff-panel::-webkit-scrollbar { width: 5px; }
3313
+ .diff-panel::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
3314
+ .diff-line {
3315
+ display: flex; gap: 0; white-space: pre; padding: 0 12px;
3316
+ }
3317
+ .diff-line.added { background: rgba(63,185,80,0.12); color: #3fb950; }
3318
+ .diff-line.removed { background: rgba(248,81,73,0.1); color: #f85149; }
3319
+ .diff-line.same { color: #484f58; }
3320
+ .diff-sign { width: 14px; flex-shrink: 0; user-select: none; }
3321
+ .diff-empty { color: #484f58; font-size: 12px; padding: 20px; text-align: center; }
3322
+ .footer {
3323
+ display: flex; gap: 8px; padding: 8px 12px;
3324
+ border-top: 1px solid #21262d; justify-content: flex-end;
3325
+ background: #161b22;
3326
+ }
3327
+ button {
3328
+ padding: 6px 14px; border: 1px solid #30363d; border-radius: 6px; cursor: pointer;
3329
+ font-size: 12px; font-weight: 500; background: #21262d; color: #8b949e;
3330
+ transition: background 0.15s, color 0.12s;
3331
+ }
3332
+ button:hover { background: #30363d; color: #e6edf3; }
3333
+ .btn-save { background: #3fb950; border-color: #3fb950; color: #fff; }
3334
+ .btn-save:hover { background: #2ea043; border-color: #2ea043; }
3335
+ .btn-launch { background: transparent; border-color: #e3b341; color: #e3b341; }
3336
+ .btn-launch:hover { background: rgba(227,179,65,0.1); }
3337
+ .btn-diff-active { background: rgba(47,129,247,0.15); border-color: rgba(47,129,247,0.4); color: #2f81f7; }
3338
+ `;
3339
+
3340
+ // src/components/file-preview/file-preview.template.ts
3341
+ function computeDiff(original, modified) {
3342
+ const MAX_LINES = 600;
3343
+ const a = original.split("\n").slice(0, MAX_LINES);
3344
+ const b = modified.split("\n").slice(0, MAX_LINES);
3345
+ const m = a.length, n = b.length;
3346
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
3347
+ for (let i2 = 1; i2 <= m; i2++) {
3348
+ for (let j2 = 1; j2 <= n; j2++) {
3349
+ dp[i2][j2] = a[i2 - 1] === b[j2 - 1] ? dp[i2 - 1][j2 - 1] + 1 : Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
3350
+ }
3351
+ }
3352
+ const result = [];
3353
+ let i = m, j = n;
3354
+ while (i > 0 || j > 0) {
3355
+ if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
3356
+ result.unshift({ type: "same", line: a[i - 1] });
3357
+ i--;
3358
+ j--;
3359
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
3360
+ result.unshift({ type: "added", line: b[j - 1] });
3361
+ j--;
3362
+ } else {
3363
+ result.unshift({ type: "removed", line: a[i - 1] });
3364
+ i--;
3365
+ }
3366
+ }
3367
+ return result;
3368
+ }
3369
+ function buildDiffHtml(original, current, t) {
3370
+ if (original === current) return `<div class="diff-empty">${t("FILE_PREVIEW.NO_CHANGES")}</div>`;
3371
+ const diff = computeDiff(original, current);
3372
+ if (!diff.length) return `<div class="diff-empty">${t("FILE_PREVIEW.NO_CHANGES_SHORT")}</div>`;
3373
+ const CONTEXT = 2;
3374
+ const compressed = [];
3375
+ let sameRun = 0;
3376
+ for (let idx = 0; idx < diff.length; idx++) {
3377
+ if (diff[idx].type === "same") {
3378
+ sameRun++;
3379
+ if (sameRun <= CONTEXT) compressed.push(diff[idx]);
3380
+ else if (idx + 1 < diff.length && diff[idx + 1].type !== "same") {
3381
+ if (sameRun > CONTEXT * 2) compressed.push({ type: "same", line: "\u2026" });
3382
+ compressed.push(diff[idx]);
3383
+ }
3384
+ } else {
3385
+ sameRun = 0;
3386
+ compressed.push(diff[idx]);
3387
+ }
3388
+ }
3389
+ return compressed.map(({ type, line }) => {
3390
+ const sign = type === "added" ? "+" : type === "removed" ? "\u2212" : " ";
3391
+ return `<div class="diff-line ${type}"><span class="diff-sign">${sign}</span><span>${escHtml(line)}</span></div>`;
3392
+ }).join("");
3393
+ }
3394
+ function renderFilePreview(state, t) {
3395
+ const { fileName, showDiff, fileContent, originalContent, currentContent, itBlock, interceptorsBlock, closeLabel } = state;
3396
+ const blocksPanelHtml = itBlock || interceptorsBlock ? `
3397
+ <div class="blocks-panel">
3398
+ ${itBlock ? `
3399
+ <div class="block-section">
3400
+ <div class="block-row">
3401
+ <span class="block-label">${t("FILE_PREVIEW.BLOCK_IT")}</span>
3402
+ <button id="btn-copy-it" class="btn-copy-sm">${t("FILE_PREVIEW.COPY_BTN")}</button>
3403
+ </div>
3404
+ <pre class="block-pre">${escHtml(itBlock.slice(0, 2e3))}</pre>
3405
+ </div>` : ""}
3406
+ ${interceptorsBlock ? `
3407
+ <div class="block-section">
3408
+ <div class="block-row">
3409
+ <span class="block-label">${t("FILE_PREVIEW.BLOCK_BEFORE_EACH")}</span>
3410
+ <button id="btn-copy-icp" class="btn-copy-sm">${t("FILE_PREVIEW.COPY_BTN")}</button>
3411
+ </div>
3412
+ <pre class="block-pre" style="color:#3fb950">${escHtml(interceptorsBlock.slice(0, 2e3))}</pre>
3413
+ </div>` : ""}
3414
+ </div>` : "";
3415
+ const hasChanges = originalContent !== null && originalContent !== (fileContent ?? "");
3416
+ return `
3417
+ <div class="container">
3418
+ <div class="header">
3419
+ <span class="file-name">\u{1F4C4} ${fileName ? escHtml(fileName) : t("FILE_PREVIEW.NO_FILE")}</span>
3420
+ <button id="btn-copy" title="${t("FILE_PREVIEW.COPY_TITLE")}">\u{1F4CB}</button>
3421
+ </div>
3422
+ <div class="body">
3423
+ ${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea>`}
3424
+ ${blocksPanelHtml}
3425
+ </div>
3426
+ <div class="footer">
3427
+ <button id="btn-launch" class="btn-launch">${t("FILE_PREVIEW.LAUNCH_BTN")}</button>
3428
+ ${hasChanges ? `<button id="btn-diff" class="${showDiff ? "btn-diff-active" : ""}">${t("FILE_PREVIEW.DIFF_BTN")}</button>` : ""}
3429
+ <button id="btn-save" class="btn-save">${t("FILE_PREVIEW.SAVE_BTN")}</button>
3430
+ <button id="btn-close">${escHtml(closeLabel || t("FILE_PREVIEW.CLOSE_BTN"))}</button>
3431
+ </div>
3432
+ </div>`;
3433
+ }
3434
+
3435
+ // src/components/file-preview/file-preview.ts
3436
+ var FilePreviewElement = class extends HTMLElement {
3437
+ shadow;
3438
+ textarea = null;
3439
+ fileName = null;
3440
+ closeLabel = "";
3441
+ translation = translationService;
3442
+ itBlock = "";
3443
+ interceptorsBlock = "";
3444
+ commands = [];
3445
+ interceptors = [];
3446
+ _fileContent = null;
3447
+ _originalContent = null;
3448
+ _showDiff = false;
3449
+ constructor() {
3450
+ super();
3451
+ this.shadow = this.attachShadow({ mode: "open" });
3452
+ }
3453
+ connectedCallback() {
3454
+ if (!this.closeLabel) this.closeLabel = this.translation.translate("FILE_PREVIEW.CLOSE_BTN");
3455
+ this.render();
3456
+ }
3457
+ t(key) {
3458
+ return this.translation.translate(key);
3459
+ }
3460
+ get fileContent() {
3461
+ return this._fileContent;
3462
+ }
3463
+ set fileContent(v) {
3464
+ if (this._originalContent === null) this._originalContent = v;
3465
+ this._fileContent = v;
3466
+ if (this.textarea) this.textarea.value = v ?? "";
3467
+ else this.render();
3468
+ }
3469
+ saveFile() {
3470
+ const content = this.textarea?.value ?? this._fileContent ?? "";
3471
+ this.dispatchEvent(new CustomEvent("save", { detail: content, bubbles: true, composed: true }));
3472
+ }
3473
+ onClose() {
3474
+ this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
3475
+ }
3476
+ async launchTest(specPath) {
3477
+ const path = specPath ?? (this.fileName ? `cypress/e2e/${this.fileName}` : "");
3478
+ if (!path) return;
3479
+ const response = await fetch("http://localhost:8123/run-test", {
3480
+ method: "POST",
3481
+ headers: { "Content-Type": "application/json" },
3482
+ body: JSON.stringify({ specPath: path })
3483
+ });
3484
+ await response.json();
3485
+ }
3486
+ copyToClipboard(text) {
3487
+ navigator.clipboard?.writeText(text);
3488
+ }
3489
+ toggleDiff() {
3490
+ this._showDiff = !this._showDiff;
3491
+ this.render();
3492
+ }
3493
+ render() {
3494
+ this.shadow.innerHTML = `<style>${FILE_PREVIEW_STYLES}</style>${renderFilePreview({
3495
+ fileName: this.fileName,
3496
+ showDiff: this._showDiff,
3497
+ fileContent: this._fileContent,
3498
+ originalContent: this._originalContent,
3499
+ currentContent: this.textarea?.value ?? this._fileContent ?? "",
3500
+ itBlock: this.itBlock,
3501
+ interceptorsBlock: this.interceptorsBlock,
3502
+ closeLabel: this.closeLabel
3503
+ }, this.t.bind(this))}`;
3504
+ this.textarea = this.shadow.getElementById("editor");
3505
+ if (this.textarea) {
3506
+ const ta = this.textarea;
3507
+ this.textarea.addEventListener("input", () => {
3508
+ this._fileContent = ta.value;
3509
+ });
3510
+ }
3511
+ this.shadow.getElementById("btn-save")?.addEventListener("click", () => this.saveFile());
3512
+ this.shadow.getElementById("btn-close")?.addEventListener("click", () => this.onClose());
3513
+ this.shadow.getElementById("btn-launch")?.addEventListener("click", () => this.launchTest());
3514
+ this.shadow.getElementById("btn-copy")?.addEventListener("click", () => {
3515
+ this.copyToClipboard(this.textarea?.value ?? this._fileContent ?? "");
3516
+ });
3517
+ this.shadow.getElementById("btn-diff")?.addEventListener("click", () => this.toggleDiff());
3518
+ this.shadow.getElementById("btn-copy-it")?.addEventListener("click", () => {
3519
+ this.copyToClipboard(this.itBlock);
3520
+ });
3521
+ this.shadow.getElementById("btn-copy-icp")?.addEventListener("click", () => {
3522
+ this.copyToClipboard(this.interceptorsBlock);
3523
+ });
3524
+ }
3525
+ };
3526
+ if (!customElements.get("file-preview")) {
3527
+ customElements.define("file-preview", FilePreviewElement);
3528
+ }
3529
+
3530
+ // src/components/lib-e2e-recorder/lib-e2e-recorder.ts
3531
+ import Swal from "sweetalert2";
3532
+
3533
+ // src/components/lib-e2e-recorder/lib-e2e-recorder.styles.ts
3534
+ function getRecorderStyles(rec, paused) {
3535
+ return `
3536
+ :host { all: initial; }
3537
+ *, *::before, *::after { box-sizing: border-box; }
3538
+
3539
+ /*
3540
+ * Invisible 190\xD7190 hit area anchored at bottom-right.
3541
+ * Keeps :hover alive while the cursor travels from the
3542
+ * toggle to any of the radial action buttons.
3543
+ */
3544
+ .widget {
3545
+ position: fixed;
3546
+ bottom: 0;
3547
+ right: 0;
3548
+ width: 190px;
3549
+ height: 190px;
3550
+ z-index: 2147483647;
3551
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
3552
+ }
3553
+
3554
+ /* \u2500\u2500 Toggle \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\u2500\u2500\u2500\u2500\u2500 */
3555
+ .btn-toggle {
3556
+ position: absolute;
3557
+ bottom: 24px;
3558
+ right: 24px;
3559
+ width: 44px;
3560
+ height: 44px;
3561
+ border-radius: 50%;
3562
+ border: none;
3563
+ cursor: pointer;
3564
+ font-size: 19px;
3565
+ background: ${rec ? "linear-gradient(135deg,#f85149 0%,#da3633 100%)" : "linear-gradient(135deg,#2f81f7 0%,#1f6feb 100%)"};
3566
+ color: #fff;
3567
+ box-shadow: ${rec ? "0 4px 20px rgba(248,81,73,.55),0 0 0 4px rgba(248,81,73,.13)" : "0 4px 20px rgba(47,129,247,.45),0 0 0 4px rgba(47,129,247,.11)"};
3568
+ display: flex;
3569
+ align-items: center;
3570
+ justify-content: center;
3571
+ transition: transform .18s, box-shadow .2s;
3572
+ z-index: 2;
3573
+ ${rec ? "animation: toggle-pulse 2s ease-in-out infinite;" : ""}
3574
+ }
3575
+ .btn-toggle:hover { transform: scale(1.1); }
3576
+ .btn-toggle:active { transform: scale(0.93); }
3577
+
3578
+ @keyframes toggle-pulse {
3579
+ 0%,100% { box-shadow: 0 4px 20px rgba(248,81,73,.55),0 0 0 4px rgba(248,81,73,.13); }
3580
+ 50% { box-shadow: 0 6px 28px rgba(248,81,73,.75),0 0 0 8px rgba(248,81,73,.06); }
3581
+ }
3582
+
3583
+ /* \u2500\u2500 Pause button \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 */
3584
+ .btn-pause {
3585
+ position: absolute;
3586
+ bottom: 78px;
3587
+ right: 24px;
3588
+ width: 34px;
3589
+ height: 34px;
3590
+ border-radius: 50%;
3591
+ border: none;
3592
+ cursor: pointer;
3593
+ font-size: 15px;
3594
+ display: ${rec ? "flex" : "none"};
3595
+ align-items: center;
3596
+ justify-content: center;
3597
+ background: ${paused ? "linear-gradient(135deg,#2f81f7 0%,#1f6feb 100%)" : "rgba(13,17,23,0.85)"};
3598
+ color: ${paused ? "#fff" : "#e3b341"};
3599
+ box-shadow: 0 2px 12px rgba(0,0,0,.4), 0 0 0 1px rgba(48,54,61,.8);
3600
+ transition: transform .15s, background .2s, color .2s;
3601
+ z-index: 2;
3602
+ }
3603
+ .btn-pause:hover { transform: scale(1.12); }
3604
+ .btn-pause:active { transform: scale(0.93); }
3605
+
3606
+ /* \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 */
3607
+ /*
3608
+ * All three start centered on the toggle button
3609
+ * (toggle center = bottom:46px right:46px from widget edge;
3610
+ * button half = 18px \u2192 bottom:28px right:28px).
3611
+ */
3612
+ .btn-action {
3613
+ position: absolute;
3614
+ bottom: 28px;
3615
+ right: 28px;
3616
+ width: 36px;
3617
+ height: 36px;
3618
+ border-radius: 50%;
3619
+ border: none;
3620
+ cursor: pointer;
3621
+ font-size: 16px;
3622
+ display: flex;
3623
+ align-items: center;
3624
+ justify-content: center;
3625
+ background: rgba(13,17,23,.92);
3626
+ backdrop-filter: blur(14px);
3627
+ -webkit-backdrop-filter: blur(14px);
3628
+ color: #8b949e;
3629
+ box-shadow: 0 4px 18px rgba(0,0,0,.45), 0 0 0 1px rgba(48,54,61,.75);
3630
+ opacity: 0;
3631
+ transform: scale(0.35);
3632
+ pointer-events: none;
3633
+ /* Collapse: fast, no spring */
3634
+ transition: opacity .15s, transform .18s ease-in,
3635
+ background .15s, color .12s, box-shadow .15s;
3636
+ }
3637
+
3638
+ /* Label to the left of each button */
3639
+ .btn-action::after {
3640
+ content: attr(data-label);
3641
+ position: absolute;
3642
+ right: calc(100% + 9px);
3643
+ top: 50%;
3644
+ transform: translateY(-50%);
3645
+ background: rgba(13,17,23,.95);
3646
+ color: #e6edf3;
3647
+ font-size: 11px;
3648
+ font-weight: 500;
3649
+ padding: 3px 9px;
3650
+ border-radius: 6px;
3651
+ white-space: nowrap;
3652
+ pointer-events: none;
3653
+ opacity: 0;
3654
+ transition: opacity .15s .05s;
3655
+ border: 1px solid rgba(48,54,61,.8);
3656
+ box-shadow: 0 2px 8px rgba(0,0,0,.35);
3657
+ }
3658
+ /* Button 1 (top) \u2014 label above instead of left to avoid overlap with btn 2 */
3659
+ .btn-action[data-n="1"]::after {
3660
+ right: auto;
3661
+ left: 50%;
3662
+ top: auto;
3663
+ bottom: calc(100% + 9px);
3664
+ transform: translateX(-50%);
3665
+ }
3666
+ .btn-action:hover::after { opacity: 1; }
3667
+ .btn-action:hover { background: #21262d; color: #e6edf3; }
3668
+ .btn-action:active { background: #30363d !important; }
3669
+
3670
+ /* Expand: spring + stagger */
3671
+ .widget:hover .btn-action {
3672
+ opacity: 1;
3673
+ pointer-events: all;
3674
+ transition: opacity .2s, transform .32s cubic-bezier(.34,1.56,.64,1),
3675
+ background .15s, color .12s, box-shadow .15s;
3676
+ }
3677
+
3678
+ /* Arc positions \u2014 4 buttons, 30\xB0 spacing, radius 90px */
3679
+ .widget:hover .btn-action[data-n="1"] { /* 0\xB0 \u2014 straight up */
3680
+ transform: translateY(-90px) scale(1);
3681
+ transition-delay: .03s;
3682
+ }
3683
+ .widget:hover .btn-action[data-n="2"] { /* 30\xB0 \u2014 upper-left */
3684
+ transform: translate(-45px,-78px) scale(1);
3685
+ transition-delay: .07s;
3686
+ }
3687
+ .widget:hover .btn-action[data-n="3"] { /* 60\xB0 \u2014 left-upper */
3688
+ transform: translate(-78px,-45px) scale(1);
3689
+ transition-delay: .11s;
3690
+ }
3691
+ .widget:hover .btn-action[data-n="4"] { /* 90\xB0 \u2014 straight left */
3692
+ transform: translateX(-90px) scale(1);
3693
+ transition-delay: .15s;
3694
+ }
3695
+ /* Button 4 (pure left) \u2014 label above to avoid going off-screen */
3696
+ .btn-action[data-n="4"]::after {
3697
+ right: auto;
3698
+ left: 50%;
3699
+ top: auto;
3700
+ bottom: calc(100% + 9px);
3701
+ transform: translateX(-50%);
3702
+ }
3703
+
3704
+ /* \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 */
3705
+ .rec-badge {
3706
+ position: fixed;
3707
+ top: 14px;
3708
+ left: 50%;
3709
+ transform: translateX(-50%);
3710
+ background: ${paused ? "linear-gradient(90deg,#e3b341,#d29922)" : "linear-gradient(90deg,#f85149,#da3633)"};
3711
+ color: #fff;
3712
+ padding: 3px 16px;
3713
+ border-radius: 20px;
3714
+ font-size: 10px;
3715
+ font-weight: 700;
3716
+ letter-spacing: 2px;
3717
+ text-transform: uppercase;
3718
+ z-index: 2147483647;
3719
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
3720
+ box-shadow: ${paused ? "0 4px 16px rgba(227,179,65,.4)" : "0 4px 16px rgba(248,81,73,.4)"};
3721
+ display: ${rec ? "block" : "none"};
3722
+ ${!paused ? "animation: rec-pulse 1.8s ease-in-out infinite;" : ""}
3723
+ }
3724
+ @keyframes rec-pulse {
3725
+ 0%,100% { opacity: 1; }
3726
+ 50% { opacity: 0.6; }
3727
+ }
3728
+ `;
3729
+ }
3730
+
3731
+ // src/components/lib-e2e-recorder/lib-e2e-recorder.template.ts
3732
+ function renderRecorderWidget(rec, paused, t) {
3733
+ return `
3734
+ <div class="widget">
3735
+ <button class="btn-action" data-n="1" data-action="config"
3736
+ data-label="${t("RECORDER.BTN_CONFIG")}">\u2699\uFE0F</button>
3737
+ <button class="btn-action" data-n="2" data-action="browse"
3738
+ data-label="${t("RECORDER.BTN_FILES")}">\u{1F4C1}</button>
3739
+ <button class="btn-action" data-n="3" data-action="commands"
3740
+ data-label="${t("RECORDER.BTN_COMMANDS")}">\u2328\uFE0F</button>
3741
+ <button class="btn-action" data-n="4" data-action="tests"
3742
+ data-label="${t("RECORDER.BTN_TESTS")}">\u{1F4CB}</button>
3743
+ <button class="btn-pause" data-action="pause"
3744
+ title="${paused ? t("RECORDER.RESUME_TITLE") : t("RECORDER.PAUSE_TITLE")}">
3745
+ ${paused ? "\u25B6" : "\u23F8"}
3746
+ </button>
3747
+ <button class="btn-toggle" data-action="toggle"
3748
+ title="${rec ? t("RECORDER.STOP_TITLE") : t("RECORDER.START_TITLE")}">
3749
+ ${rec ? "\u23F9" : "\u23FA"}
3750
+ </button>
3751
+ </div>
3752
+ <div class="rec-badge">${paused ? t("RECORDER.BADGE_PAUSED") : t("RECORDER.BADGE_REC")}</div>`;
3753
+ }
3754
+
3755
+ // src/components/lib-e2e-recorder/lib-e2e-recorder.ts
3756
+ var LibE2eRecorderElement = class extends HTMLElement {
3757
+ shadow;
3758
+ keydownHandler;
3759
+ recordingUnsub;
3760
+ commandsUnsub;
3761
+ interceptorsUnsub;
3762
+ pauseUnsub;
3763
+ selectorNotFoundUnsub;
3764
+ controlFirstTimeData = true;
3765
+ _previsualizerRef = null;
3766
+ httpMonitor;
3767
+ smartSelectorEnabled = true;
3768
+ recording;
3769
+ persistence;
3770
+ translation;
3771
+ isRecording = false;
3772
+ isPaused = false;
3773
+ cypressCommands = [];
3774
+ interceptors = [];
3775
+ isCommandsDialogOpen = false;
3776
+ isSavedTestsDialogOpen = false;
3777
+ isSaveTestDialogOpen = false;
3778
+ isSettingsDialogOpen = false;
3779
+ isAdvancedEditorDialogOpen = false;
3780
+ constructor() {
3781
+ super();
3782
+ this.shadow = this.attachShadow({ mode: "open" });
3783
+ }
3784
+ connectedCallback() {
3785
+ if (!this.getAttribute("data-cy")) {
3786
+ this.setAttribute("data-cy", "lib-e2e-cypress-for-dummys");
3787
+ }
3788
+ if (!this.recording) this.recording = new RecordingService();
3789
+ if (!this.persistence) this.persistence = new PersistenceService();
3790
+ if (!this.translation) this.translation = new TranslationService();
3791
+ this.httpMonitor = new HttpMonitor(this.recording);
3792
+ this.httpMonitor.install();
3793
+ injectStyles(LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES, "lib-e2e-swal2-styles");
3794
+ injectStyles(SCROLLBAR_STYLES, "lib-e2e-scrollbar-styles");
3795
+ this.initHttpConfig();
3796
+ this.initLanguage();
3797
+ this.initFilesystemAccess();
3798
+ this.initSelectorStrategy();
3799
+ this.initSubscriptions();
3800
+ this.render();
3801
+ this.keydownHandler = (e) => this.handleKeyboardEvent(e);
3802
+ window.addEventListener("keydown", this.keydownHandler);
3803
+ }
3804
+ disconnectedCallback() {
3805
+ window.removeEventListener("keydown", this.keydownHandler);
3806
+ this.recordingUnsub?.();
3807
+ this.commandsUnsub?.();
3808
+ this.interceptorsUnsub?.();
3809
+ this.pauseUnsub?.();
3810
+ this.selectorNotFoundUnsub?.();
3811
+ this.httpMonitor?.uninstall();
3812
+ this.recording.destroy();
3813
+ }
3814
+ async initHttpConfig() {
3815
+ const config = await this.persistence.getConfig("extendedHttpCommands");
3816
+ if (config === null) {
3817
+ await this.persistence.setConfig({ extendedHttpCommands: "true" });
3818
+ localStorage.setItem("extendedHttpCommands", "true");
3819
+ } else {
3820
+ localStorage.setItem("extendedHttpCommands", config["extendedHttpCommands"] ?? "true");
3821
+ }
3822
+ }
3823
+ async initLanguage() {
3824
+ const config = await this.persistence.getConfig("language");
3825
+ const lang = config?.["language"];
3826
+ this.translation.setLang(lang ?? this.translation.detectLang());
3827
+ }
3828
+ initSubscriptions() {
3829
+ this.recordingUnsub = this.recording.onRecordingChange((val) => {
3830
+ this.isRecording = val;
3831
+ if (!val && !this.controlFirstTimeData) {
3832
+ this.saveRecordingHistory();
3833
+ this.showSaveTestDialog();
3834
+ }
3835
+ this.controlFirstTimeData = false;
3836
+ this.render();
3837
+ });
3838
+ this.commandsUnsub = this.recording.onCommandsChange((cmds) => {
3839
+ this.cypressCommands = cmds;
3840
+ if (this._previsualizerRef) this._previsualizerRef.commands = cmds;
3841
+ });
3842
+ this.interceptorsUnsub = this.recording.onInterceptorsChange((icps) => {
3843
+ this.interceptors = icps;
3844
+ if (this._previsualizerRef) this._previsualizerRef.interceptors = icps;
3845
+ });
3846
+ this.pauseUnsub = this.recording.onPauseChange((paused) => {
3847
+ this.isPaused = paused;
3848
+ this.render();
3849
+ });
3850
+ this.selectorNotFoundUnsub = this.recording.onSelectorNotFound((target) => {
3851
+ if (this.smartSelectorEnabled) this.showSelectorPicker(target);
3852
+ });
3853
+ }
3854
+ showSelectorPicker(target) {
3855
+ import("./selector-picker-VJOLGZ5H.js").then(() => {
3856
+ const existing = document.querySelector("selector-picker");
3857
+ if (existing) existing.remove();
3858
+ const picker = document.createElement("selector-picker");
3859
+ picker.targetElement = target;
3860
+ picker.recording = this.recording;
3861
+ picker.translation = this.translation;
3862
+ document.body.appendChild(picker);
3863
+ });
3864
+ }
3865
+ async initSelectorStrategy() {
3866
+ const config = await this.persistence.getGeneralConfig();
3867
+ const strategy = config?.["selectorStrategy"];
3868
+ if (strategy) this.recording.selectorStrategy = strategy;
3869
+ this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
3870
+ }
3871
+ toggle() {
3872
+ this.recording.toggleRecording();
3873
+ }
3874
+ togglePause() {
3875
+ this.recording.togglePause();
3876
+ }
3877
+ setLanguage(lang) {
3878
+ const resolved = lang ? lang : this.translation.detectLang();
3879
+ this.translation.setLang(resolved);
3880
+ }
3881
+ handleKeyboardEvent(event) {
3882
+ if (!event.ctrlKey) return;
3883
+ const key = event.key.toLowerCase();
3884
+ if (key === "r") {
3885
+ event.preventDefault();
3886
+ this.toggle();
3887
+ } else if (key === "p") {
3888
+ event.preventDefault();
3889
+ this.togglePause();
3890
+ } else if (key === "1") {
3891
+ event.preventDefault();
3892
+ this.showSavedTestsDialog();
3893
+ } else if (key === "2") {
3894
+ event.preventDefault();
3895
+ this.showCommandsDialog();
3896
+ } else if (key === "3") {
3897
+ event.preventDefault();
3898
+ this.showSettingsDialog();
3899
+ }
3900
+ }
3901
+ // ── recording history (task 5) ────────────────────────────────────────────
3902
+ saveRecordingHistory() {
3903
+ if (!this.cypressCommands.length) return;
3904
+ try {
3905
+ const existing = JSON.parse(localStorage.getItem("e2e-recording-history") ?? "[]");
3906
+ existing.unshift({ commands: this.cypressCommands, interceptors: this.interceptors, savedAt: Date.now() });
3907
+ if (existing.length > 5) existing.splice(5);
3908
+ localStorage.setItem("e2e-recording-history", JSON.stringify(existing));
3909
+ } catch {
3910
+ }
3911
+ }
3912
+ recoverLastRecording() {
3913
+ try {
3914
+ const existing = JSON.parse(localStorage.getItem("e2e-recording-history") ?? "[]");
3915
+ if (!existing.length) return;
3916
+ const { commands, interceptors } = existing[0];
3917
+ commands.forEach((cmd) => this.recording.appendCommand(cmd));
3918
+ interceptors.forEach((_icp) => {
3919
+ });
3920
+ } catch {
3921
+ }
3922
+ }
3923
+ clearRecordingHistory() {
3924
+ localStorage.removeItem("e2e-recording-history");
3925
+ }
3926
+ getRecordingHistory() {
3927
+ try {
3928
+ return JSON.parse(localStorage.getItem("e2e-recording-history") ?? "[]");
3929
+ } catch {
3930
+ return [];
3931
+ }
3932
+ }
3933
+ // ── filesystem setup ─────────────────────────────────────────────────────
3934
+ async initFilesystemAccess() {
3935
+ const config = await this.persistence.getGeneralConfig();
3936
+ if (config && "allowReadWriteFiles" in config) return;
3937
+ this.showFilesystemSetupDialog();
3938
+ }
3939
+ showFilesystemSetupDialog() {
3940
+ Swal.fire({
3941
+ title: this.translation.translate("RECORDER.FS_TITLE"),
3942
+ html: '<div id="fs-setup-content"></div>',
3943
+ showCloseButton: true,
3944
+ showConfirmButton: false,
3945
+ didOpen: () => {
3946
+ makeSwalDraggable();
3947
+ setSwal2DataCyAttribute();
3948
+ const container = document.getElementById("fs-setup-content");
3949
+ if (!container) return;
3950
+ container.innerHTML = `
3951
+ <div style="padding:16px 20px 20px;color:#8b949e;font-size:13px;line-height:1.7">
3952
+ <p>${this.translation.translate("RECORDER.FS_INTRO_HTML")}</p>
3953
+ <p style="margin-top:10px;margin-bottom:6px;font-size:11px;color:#8b949e">
3954
+ ${this.translation.translate("RECORDER.FS_STRUCTURE_HINT_HTML")}
3955
+ </p>
3956
+ <pre style="margin:0;padding:10px 14px;background:#0d1117;border:1px solid #21262d;
3957
+ border-radius:8px;font-size:11px;color:#c9d1d9;line-height:1.8;
3958
+ font-family:'Cascadia Code','Fira Code','Consolas',monospace">
3959
+ cypress/ <span style="color:#484f58">${this.translation.translate("RECORDER.FS_TREE_PICK_HINT")}</span>
3960
+ \u2514\u2500\u2500 e2e/ <span style="color:#484f58">${this.translation.translate("RECORDER.FS_TREE_READ_HINT")}</span>
3961
+ \u2514\u2500\u2500 *.cy.ts</pre>
3962
+ <p style="margin-top:8px;font-size:11px;color:#484f58">
3963
+ ${this.translation.translate("RECORDER.FS_PERMISSION_NOTE")}
3964
+ </p>
3965
+ <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:20px">
3966
+ <button id="fs-skip"
3967
+ style="padding:7px 16px;border:1px solid #30363d;border-radius:6px;cursor:pointer;
3968
+ font-size:12px;font-weight:500;background:transparent;color:#8b949e">
3969
+ ${this.translation.translate("RECORDER.FS_LATER_BTN")}
3970
+ </button>
3971
+ <button id="fs-select"
3972
+ style="padding:7px 16px;border:none;border-radius:6px;cursor:pointer;
3973
+ font-size:12px;font-weight:500;background:#2f81f7;color:#fff">
3974
+ ${this.translation.translate("RECORDER.FS_SELECT_BTN")}
3975
+ </button>
3976
+ </div>
3977
+ </div>`;
3978
+ document.getElementById("fs-skip")?.addEventListener("click", async () => {
3979
+ await this.persistence.setConfigKey("allowReadWriteFiles", "false");
3980
+ Swal.close();
3981
+ });
3982
+ document.getElementById("fs-select")?.addEventListener("click", async () => {
3983
+ try {
3984
+ await this.persistence.requestDirectoryPermissions();
3985
+ Swal.close();
3986
+ showToast(this.translation.translate("RECORDER.FS_SUCCESS_TOAST"));
3987
+ } catch (e) {
3988
+ if (e?.name !== "AbortError") {
3989
+ showToast(this.translation.translate("RECORDER.FS_ERROR_TOAST"), false);
3990
+ }
3991
+ }
3992
+ });
3993
+ }
3994
+ });
3995
+ }
3996
+ // ── dialogs ──────────────────────────────────────────────────────────────
3997
+ showCommandsDialog() {
3998
+ this.toggleModal("isCommandsDialogOpen", () => {
3999
+ Swal.fire({
4000
+ title: this.translation.translate("MAIN_FRAME.DIALOG_COMMANDS"),
4001
+ html: '<div id="commands-modal-content" style="min-height:250px;padding:0"></div>',
4002
+ showCloseButton: true,
4003
+ showConfirmButton: false,
4004
+ allowOutsideClick: false,
4005
+ backdrop: false,
4006
+ width: 640,
4007
+ color: "#e6edf3",
4008
+ didOpen: () => {
4009
+ makeSwalDraggable();
4010
+ setSwal2DataCyAttribute();
4011
+ const swalContainer = document.querySelector(".swal2-container");
4012
+ if (swalContainer) {
4013
+ swalContainer.style.pointerEvents = "none";
4014
+ const popup = swalContainer.querySelector(".swal2-popup");
4015
+ if (popup) popup.style.pointerEvents = "all";
4016
+ }
4017
+ const container = document.getElementById("commands-modal-content");
4018
+ if (!container) return;
4019
+ const child = document.createElement("test-previsualizer");
4020
+ child.translation = this.translation;
4021
+ child.commands = this.cypressCommands;
4022
+ child.interceptors = this.interceptors;
4023
+ child.editable = true;
4024
+ container.appendChild(child);
4025
+ this._previsualizerRef = child;
4026
+ child.addEventListener("deletecommand", (e) => {
4027
+ this.recording.removeCommand(e.detail);
4028
+ });
4029
+ child.addEventListener("movecommand", (e) => {
4030
+ this.recording.moveCommand(e.detail.from, e.detail.to);
4031
+ });
4032
+ child.addEventListener("deleteinterceptor", (e) => {
4033
+ this.recording.removeInterceptor(e.detail);
4034
+ });
4035
+ const assertionHtml = `
4036
+ <div id="assertion-builder"
4037
+ style="padding:10px 12px;border-top:1px solid #21262d;background:#0d1117">
4038
+ <div style="font-size:10px;color:#484f58;text-transform:uppercase;letter-spacing:.8px;font-weight:600;margin-bottom:8px">
4039
+ ${this.translation.translate("RECORDER.ASSERTION_SECTION")}
4040
+ </div>
4041
+ <div style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end">
4042
+ <input id="assert-selector" type="text"
4043
+ placeholder="${this.translation.translate("RECORDER.ASSERT_SELECTOR_PH").replace(/"/g, "&quot;")}"
4044
+ style="flex:2;min-width:140px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
4045
+ border-radius:5px;color:#c9d1d9;font-size:11px;outline:none;
4046
+ font-family:'Cascadia Code','Fira Code',monospace"/>
4047
+ <select id="assert-type"
4048
+ style="padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;
4049
+ color:#c9d1d9;font-size:11px;outline:none;cursor:pointer;flex-shrink:0">
4050
+ <option value="be.visible">be.visible</option>
4051
+ <option value="not.exist">not.exist</option>
4052
+ <option value="be.disabled">be.disabled</option>
4053
+ <option value="be.checked">be.checked</option>
4054
+ <option value="contain.text">contain.text</option>
4055
+ <option value="have.value">have.value</option>
4056
+ <option value="have.length">have.length</option>
4057
+ <option value="have.class">have.class</option>
4058
+ <option value="have.attr">have.attr</option>
4059
+ </select>
4060
+ <input id="assert-value" type="text" placeholder="${this.translation.translate("RECORDER.ASSERT_VALUE_PH")}"
4061
+ style="flex:2;min-width:110px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
4062
+ border-radius:5px;color:#c9d1d9;font-size:11px;outline:none"/>
4063
+ <button id="btn-add-assertion"
4064
+ style="padding:5px 12px;border:none;border-radius:5px;cursor:pointer;
4065
+ font-size:11px;font-weight:500;background:#2f81f7;color:#fff;
4066
+ white-space:nowrap;flex-shrink:0">
4067
+ ${this.translation.translate("RECORDER.ASSERT_ADD_BTN")}
4068
+ </button>
4069
+ </div>
4070
+ </div>`;
4071
+ container.insertAdjacentHTML("beforeend", assertionHtml);
4072
+ const NO_VALUE_ASSERTIONS = /* @__PURE__ */ new Set(["be.visible", "not.exist", "be.disabled", "be.checked"]);
4073
+ document.getElementById("btn-add-assertion")?.addEventListener("click", () => {
4074
+ const sel = document.getElementById("assert-selector").value.trim();
4075
+ const type = document.getElementById("assert-type").value;
4076
+ const val = document.getElementById("assert-value").value.trim();
4077
+ if (!sel) return;
4078
+ const cmd = NO_VALUE_ASSERTIONS.has(type) || !val ? `cy.get('${sel}').should('${type}')` : `cy.get('${sel}').should('${type}', '${val}')`;
4079
+ this.recording.appendCommand(cmd);
4080
+ document.getElementById("assert-selector").value = "";
4081
+ document.getElementById("assert-value").value = "";
4082
+ });
4083
+ },
4084
+ willClose: () => {
4085
+ this.isCommandsDialogOpen = false;
4086
+ this._previsualizerRef = null;
4087
+ }
4088
+ });
4089
+ this.resizePopup();
4090
+ });
4091
+ }
4092
+ showSavedTestsDialog() {
4093
+ this.toggleModal("isSavedTestsDialogOpen", () => {
4094
+ Swal.fire({
4095
+ title: this.translation.translate("MAIN_FRAME.DIALOG_SAVED_TESTS"),
4096
+ html: '<div id="saved-tests-modal-content" style="min-height:250px;padding:0"></div>',
4097
+ showCloseButton: true,
4098
+ showConfirmButton: false,
4099
+ width: 680,
4100
+ color: "#e6edf3",
4101
+ didOpen: () => {
4102
+ makeSwalDraggable();
4103
+ setSwal2DataCyAttribute();
4104
+ const container = document.getElementById("saved-tests-modal-content");
4105
+ if (!container) return;
4106
+ const child = document.createElement("test-editor");
4107
+ child.persistence = this.persistence;
4108
+ child.translation = this.translation;
4109
+ container.appendChild(child);
4110
+ },
4111
+ willClose: () => {
4112
+ this.isSavedTestsDialogOpen = false;
4113
+ }
4114
+ });
4115
+ this.resizePopup();
4116
+ });
4117
+ }
4118
+ showSaveTestDialog() {
4119
+ this.toggleModal("isSaveTestDialogOpen", () => {
4120
+ Swal.fire({
4121
+ title: this.translation.translate("MAIN_FRAME.DIALOG_SAVE"),
4122
+ html: '<div id="save-test-modal-content" style="padding:0"></div>',
4123
+ showCloseButton: true,
4124
+ showConfirmButton: false,
4125
+ allowOutsideClick: false,
4126
+ color: "#e6edf3",
4127
+ didOpen: () => {
4128
+ makeSwalDraggable();
4129
+ setSwal2DataCyAttribute();
4130
+ const container = document.getElementById("save-test-modal-content");
4131
+ if (!container) return;
4132
+ const child = document.createElement("save-test");
4133
+ child.translation = this.translation;
4134
+ container.appendChild(child);
4135
+ child.addEventListener("savetest", (e) => {
4136
+ const { description, tags } = e.detail ?? {};
4137
+ this.onSaveTest(description ?? null, tags ?? []);
4138
+ Swal.close();
4139
+ });
4140
+ child.addEventListener("saveandexport", (e) => {
4141
+ const { description, tags } = e.detail ?? {};
4142
+ this.onSaveAndExportTest(description ?? null, tags ?? []);
4143
+ Swal.close();
4144
+ });
4145
+ },
4146
+ willClose: () => {
4147
+ this.isSaveTestDialogOpen = false;
4148
+ }
4149
+ });
4150
+ });
4151
+ }
4152
+ showSettingsDialog() {
4153
+ this.toggleModal("isSettingsDialogOpen", () => {
4154
+ Swal.fire({
4155
+ title: this.translation.translate("MAIN_FRAME.SETTINGS"),
4156
+ html: '<div id="settings-modal-content" style="padding:0"></div>',
4157
+ showCloseButton: true,
4158
+ showConfirmButton: false,
4159
+ width: 520,
4160
+ color: "#e6edf3",
4161
+ didOpen: () => {
4162
+ makeSwalDraggable();
4163
+ setSwal2DataCyAttribute();
4164
+ const container = document.getElementById("settings-modal-content");
4165
+ if (!container) return;
4166
+ const child = document.createElement("e2e-configuration");
4167
+ child.persistence = this.persistence;
4168
+ child.translation = this.translation;
4169
+ container.appendChild(child);
4170
+ child.addEventListener("smartselectorchange", (e) => {
4171
+ this.smartSelectorEnabled = e.detail;
4172
+ });
4173
+ },
4174
+ willClose: () => {
4175
+ this.isSettingsDialogOpen = false;
4176
+ }
4177
+ });
4178
+ this.resizePopup();
4179
+ });
4180
+ }
4181
+ showAdvancedEditorDialog(testId) {
4182
+ this.toggleModal("isAdvancedEditorDialogOpen", () => {
4183
+ Swal.fire({
4184
+ title: this.translation.translate("MAIN_FRAME.SHOW_ADVANCED_EDITOR"),
4185
+ html: '<div id="advanced-editor-modal-content" style="min-height:300px;padding:0"></div>',
4186
+ showCloseButton: true,
4187
+ showConfirmButton: false,
4188
+ allowOutsideClick: false,
4189
+ width: 780,
4190
+ color: "#e6edf3",
4191
+ didOpen: () => {
4192
+ makeSwalDraggable();
4193
+ setSwal2DataCyAttribute();
4194
+ const container = document.getElementById("advanced-editor-modal-content");
4195
+ if (!container) return;
4196
+ const child = document.createElement("advanced-test-editor");
4197
+ child.persistence = this.persistence;
4198
+ child.translation = this.translation;
4199
+ if (testId !== void 0) child.testId = testId;
4200
+ container.appendChild(child);
4201
+ child.addEventListener("selectorstrategychange", (e) => {
4202
+ this.recording.selectorStrategy = e.detail;
4203
+ });
4204
+ child.addEventListener("closemodal", () => Swal.close());
4205
+ child.addEventListener("openfileeditor", (e) => {
4206
+ Swal.close();
4207
+ setTimeout(() => this.showFileEditorDialog(
4208
+ e.detail.handle,
4209
+ e.detail.content,
4210
+ e.detail.fileName,
4211
+ e.detail.testId,
4212
+ e.detail.itBlock,
4213
+ e.detail.interceptorsBlock
4214
+ ), 150);
4215
+ });
4216
+ },
4217
+ willClose: () => {
4218
+ this.isAdvancedEditorDialogOpen = false;
4219
+ }
4220
+ });
4221
+ this.resizePopup();
4222
+ });
4223
+ }
4224
+ showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "") {
4225
+ Swal.fire({
4226
+ title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
4227
+ html: '<div id="file-editor-modal-content" style="padding:0;height:540px"></div>',
4228
+ showCloseButton: false,
4229
+ showConfirmButton: false,
4230
+ allowOutsideClick: false,
4231
+ width: 1100,
4232
+ color: "#e6edf3",
4233
+ didOpen: () => {
4234
+ makeSwalDraggable();
4235
+ setSwal2DataCyAttribute();
4236
+ const container = document.getElementById("file-editor-modal-content");
4237
+ if (!container) return;
4238
+ const child = document.createElement("file-preview");
4239
+ child.translation = this.translation;
4240
+ child.fileContent = content;
4241
+ child.fileName = fileName;
4242
+ child.closeLabel = this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR");
4243
+ child.itBlock = itBlock;
4244
+ child.interceptorsBlock = interceptorsBlock;
4245
+ container.appendChild(child);
4246
+ child.addEventListener("close", () => {
4247
+ Swal.close();
4248
+ setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
4249
+ });
4250
+ child.addEventListener("save", async (e) => {
4251
+ try {
4252
+ const writable = await handle.createWritable();
4253
+ await writable.write(e.detail);
4254
+ await writable.close();
4255
+ showToast(this.translation.translate("RECORDER.FILE_SAVED_TOAST"));
4256
+ } catch {
4257
+ showToast(this.translation.translate("RECORDER.FILE_SAVE_ERROR_TOAST"), false);
4258
+ }
4259
+ Swal.close();
4260
+ setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
4261
+ });
4262
+ }
4263
+ });
4264
+ this.resizePopup();
4265
+ }
4266
+ // ── save handlers ─────────────────────────────────────────────────────────
4267
+ async onSaveTest(description, tags = []) {
4268
+ if (description) {
4269
+ await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags);
4270
+ this.recording.clearCommands();
4271
+ this.clearRecordingHistory();
4272
+ this.cypressCommands = [];
4273
+ this.interceptors = [];
4274
+ }
4275
+ }
4276
+ async onSaveAndExportTest(description, tags = []) {
4277
+ if (description) {
4278
+ const id = await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags);
4279
+ this.recording.clearCommands();
4280
+ this.clearRecordingHistory();
4281
+ this.cypressCommands = [];
4282
+ this.interceptors = [];
4283
+ setTimeout(() => this.showAdvancedEditorDialog(id), 300);
4284
+ }
4285
+ }
4286
+ // ── helpers ───────────────────────────────────────────────────────────────
4287
+ toggleModal(flag, openFn) {
4288
+ if (this[flag]) {
4289
+ Swal.close();
4290
+ this[flag] = false;
4291
+ return;
4292
+ }
4293
+ this[flag] = true;
4294
+ openFn();
4295
+ }
4296
+ resizePopup() {
4297
+ setTimeout(() => {
4298
+ const popup = Swal.getPopup();
4299
+ if (popup) makeModalResizable(popup, { minWidth: 400, minHeight: 200 });
4300
+ }, 0);
4301
+ }
4302
+ render() {
4303
+ const rec = this.isRecording;
4304
+ const paused = this.isPaused;
4305
+ this.shadow.innerHTML = `<style>${getRecorderStyles(rec, paused)}</style>${renderRecorderWidget(rec, paused, this.translation.translate.bind(this.translation))}`;
4306
+ this.shadow.querySelector('[data-action="toggle"]')?.addEventListener("click", () => this.toggle());
4307
+ this.shadow.querySelector('[data-action="pause"]')?.addEventListener("click", () => this.togglePause());
4308
+ this.shadow.querySelector('[data-action="tests"]')?.addEventListener("click", () => this.showSavedTestsDialog());
4309
+ this.shadow.querySelector('[data-action="commands"]')?.addEventListener("click", () => this.showCommandsDialog());
4310
+ this.shadow.querySelector('[data-action="config"]')?.addEventListener("click", () => this.showSettingsDialog());
4311
+ this.shadow.querySelector('[data-action="browse"]')?.addEventListener("click", () => this.showAdvancedEditorDialog());
4312
+ }
4313
+ };
4314
+ if (!customElements.get("lib-e2e-recorder")) {
4315
+ customElements.define("lib-e2e-recorder", LibE2eRecorderElement);
4316
+ }
4317
+
4318
+ // src/index.ts
4319
+ var VERSION = "0.1.0";
4320
+ export {
4321
+ AdvancedTestEditorElement,
4322
+ AdvancedTestTransformationService,
4323
+ ConfigurationElement,
4324
+ DB_SCHEMA,
4325
+ DB_STORE_NAMES,
4326
+ FilePreviewElement,
4327
+ HttpMonitor,
4328
+ INPUT_TYPES,
4329
+ LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
4330
+ LibE2eRecorderElement,
4331
+ PersistenceService,
4332
+ RecordingService,
4333
+ SCROLLBAR_STYLES,
4334
+ SUPPORTED_LANGS,
4335
+ SaveTestElement,
4336
+ Subject,
4337
+ TestEditorElement,
4338
+ TestPrevisualizerElement,
4339
+ TransformationService,
4340
+ TranslationService,
4341
+ VERSION,
4342
+ advancedTestTransformationService,
4343
+ generateAlias,
4344
+ injectStyles,
4345
+ isLang,
4346
+ makeModalResizable,
4347
+ makeSwalDraggable,
4348
+ makeSwalDraggableByContentId,
4349
+ persistenceService,
4350
+ setSwal2DataCyAttribute,
4351
+ showToast,
4352
+ transformationService,
4353
+ translationService
4354
+ };
4355
+ //# sourceMappingURL=index.js.map