lib-e2e-cypress-for-dummys-ts 0.9.0 → 1.0.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 CHANGED
@@ -2,8 +2,11 @@ import {
2
2
  FORBIDDEN_ID_PREFIXES,
3
3
  SelectorPickerElement,
4
4
  escAttr,
5
- escHtml
6
- } from "./chunk-CQEDHL7A.js";
5
+ escHtml,
6
+ escapeCssAttrValue,
7
+ escapeSingleQuotes,
8
+ normalizeBlock
9
+ } from "./chunk-24WFAT4W.js";
7
10
 
8
11
  // src/models/lang.model.ts
9
12
  var SUPPORTED_LANGS = ["es", "en", "fr", "it", "de"];
@@ -90,11 +93,6 @@ var DB_SCHEMA = {
90
93
  };
91
94
  var DB_STORE_NAMES = DB_SCHEMA.stores.map((s) => s.name);
92
95
 
93
- // src/models/active-session.model.ts
94
- var ACTIVE_SESSION_BREADCRUMB_KEY = "e2e-active-session";
95
- var RESUME_TTL_CONFIG_KEY = "resumeRecencyTtlMinutes";
96
- var DEFAULT_RESUME_TTL_MINUTES = 30;
97
-
98
96
  // src/utils/subject.ts
99
97
  var Subject = class {
100
98
  _value;
@@ -142,7 +140,10 @@ var I18N_ES = {
142
140
  NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc.",
143
141
  TICKET_LABEL: "Ticket / Issue (opcional):",
144
142
  TICKET_PLACEHOLDER: "Ej: PROJ-123, #42",
145
- TICKET_WARN: "ID no v\xE1lido para el proveedor configurado (se guardar\xE1 igualmente)"
143
+ TICKET_WARN: "ID no v\xE1lido para el proveedor configurado (se guardar\xE1 igualmente)",
144
+ CONFIRM_DISCARD: "\xBFSeguro que quieres descartar el test grabado? Se perder\xE1n todos los pasos registrados.",
145
+ CONFIRM_DISCARD_BTN: "S\xED, descartar",
146
+ BACK_BTN: "Volver"
146
147
  },
147
148
  TEST_EDITOR: {
148
149
  NO_TAGS: "Sin etiquetas",
@@ -224,7 +225,32 @@ var I18N_ES = {
224
225
  ISSUE_TRACKER_SUB: "A\xF1ade un ID de ticket al guardar y genera un enlace clickable en el listado.",
225
226
  ISSUE_TRACKER_PROVIDER: "Proveedor",
226
227
  ISSUE_TRACKER_BASE_URL: "URL base",
227
- ISSUE_TRACKER_BASE_URL_PH: "Ej: https://miproyecto.atlassian.net"
228
+ ISSUE_TRACKER_BASE_URL_PH: "Ej: https://miproyecto.atlassian.net",
229
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
230
+ LOGIN_SETUP_NOT_CONFIGURED: "No configurado",
231
+ LOGIN_SETUP_SET_UP_BTN: "Configurar",
232
+ LOGIN_SETUP_TITLE: "Configuraci\xF3n de login",
233
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Crear scaffold",
234
+ LOGIN_SETUP_SELECT_EXISTING: "Seleccionar fichero existente",
235
+ LOGIN_SETUP_FILE_NAME_LABEL: "Nombre del fichero",
236
+ LOGIN_SETUP_FILE_NAME_PH: "Ej: cypress/common-services/login.service.ts",
237
+ LOGIN_SETUP_RESCAN: "\u{1F504} Re-escanear",
238
+ LOGIN_SETUP_FUNCTIONS_DETECTED: "Funciones detectadas",
239
+ LOGIN_SETUP_BLOCK_ASSIGN: "Asignaci\xF3n de bloques",
240
+ LOGIN_SETUP_NO_FUNCTIONS: "No se encontraron funciones exportadas. Rellena el scaffold y re-escanea.",
241
+ LOGIN_SETUP_BEFORE_LABEL: "Funci\xF3n before() (una vez por suite)",
242
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "Funci\xF3n beforeEach() (antes de cada test)",
243
+ LOGIN_SETUP_NONE_OPTION: "\u2014 Ninguna \u2014",
244
+ LOGIN_SETUP_SUMMARY: "Fichero: {file} \xB7 before: {before} \xB7 beforeEach: {beforeEach}",
245
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Guardar",
246
+ LOGIN_SETUP_CLEAR_BTN: "Eliminar configuraci\xF3n",
247
+ LOGIN_SETUP_CANCEL_BTN: "Cancelar",
248
+ LOGIN_SETUP_SOURCE_LABEL: "Origen del fichero de login",
249
+ LOGIN_SETUP_CREATE_SUB: "Genera un scaffold con funciones de ejemplo",
250
+ LOGIN_SETUP_SELECT_SUB: "Apunta a un servicio de login existente",
251
+ LOGIN_SETUP_INJECT_TITLE: "\xBFA\xF1adir bloques de login?",
252
+ LOGIN_SETUP_INJECT_TEXT: "Este fichero no tiene los bloques de login configurados. \xBFQuieres a\xF1adir el import y los bloques before/beforeEach ahora?",
253
+ LOGIN_SETUP_INJECT_CONFIRM: "S\xED, a\xF1adir"
228
254
  },
229
255
  SELECTOR_PICKER: {
230
256
  TITLE: "Selecciona un elemento del DOM",
@@ -315,6 +341,9 @@ var I18N_ES = {
315
341
  FILE_SAVE_ERROR_TOAST: "Error al guardar el archivo",
316
342
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures escritos en cypress/fixtures",
317
343
  FIXTURES_NO_FOLDER_TOAST: "No se pudieron escribir los fixtures (configura la carpeta Cypress)",
344
+ FILE_UPLOAD_MANUAL_TOAST: "A\xF1ade {files} a cypress/fixtures/ antes de ejecutar el test.",
345
+ FILE_UPLOAD_COPIED_TOAST: "{files} copiado/s a cypress/fixtures/.",
346
+ FILE_UPLOAD_COPY_ERROR_TOAST: "No se pudo copiar {files} a cypress/fixtures/. A\xF1\xE1delo manualmente.",
318
347
  BTN_CONFIG: "Config",
319
348
  BTN_FILES: "Ficheros",
320
349
  BTN_COMMANDS: "Comandos",
@@ -362,7 +391,7 @@ var I18N_ES = {
362
391
  SEC_PANELS: "\u{1F5C2} Paneles",
363
392
  PANEL_TESTS: "\u{1F4CB} Tests \u2014 guardados: filtrar por tag, copiar, combinar, exportar.",
364
393
  PANEL_COMMANDS: "\u2328\uFE0F Comandos \u2014 de la grabaci\xF3n actual: reordenar, borrar, a\xF1adir aserciones.",
365
- PANEL_CONFIG: "\u2699\uFE0F Config \u2014 idioma, selector, HTTP, carpeta Cypress, datos, visibilidad, posici\xF3n.",
394
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 idioma, selector, HTTP, carpeta Cypress, login setup, datos, visibilidad, posici\xF3n.",
366
395
  PANEL_FILES: "\u{1F4C1} Ficheros \u2014 inserta tests en tus .cy.ts y lanza el runner.",
367
396
  SEC_SELECTOR: "\u{1F50E} Selectores",
368
397
  SELECTOR_1: "Elige el atributo preferido (data-cy, data-testid, aria-label, id) en Config.",
@@ -375,6 +404,7 @@ var I18N_ES = {
375
404
  ADV_RUNNER: "Runner local: npx lib-e2e-cypress-runner para lanzar un spec headless desde el editor.",
376
405
  ADV_CROSSAPP: "Cross-app: la grabaci\xF3n sobrevive al cambiar de micro-frontend (mismo origen).",
377
406
  ADV_DRAG: "Arrastra el bot\xF3n \u23FA para mover el widget; recuerda su posici\xF3n.",
407
+ ADV_LOGIN: "Login Setup (Ctrl+3): apunta a tu servicio de login; el grabador inyecta before()/beforeEach() autom\xE1ticamente en cada test nuevo.",
378
408
  CLOSE: "Cerrar",
379
409
  TAB_REFERENCE: "Referencia r\xE1pida",
380
410
  TAB_GUIDE: "Gu\xEDa de uso",
@@ -392,13 +422,13 @@ var I18N_ES = {
392
422
  G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
393
423
  G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
394
424
  G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
425
+ G_COV_7: "Login Setup: configura tu funci\xF3n de login en Ctrl+3 \u2192 Login Setup y el grabador a\xF1ade los bloques before()/beforeEach() a cada test nuevo.",
395
426
  G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
396
427
  G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
397
428
  G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
398
429
  G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
399
430
  G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
400
- G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
401
- G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
431
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse."
402
432
  }
403
433
  };
404
434
 
@@ -429,7 +459,10 @@ var I18N_EN = {
429
459
  NOTES_PLACEHOLDER: "What this test validates, preconditions, etc.",
430
460
  TICKET_LABEL: "Ticket / Issue (optional):",
431
461
  TICKET_PLACEHOLDER: "E.g.: PROJ-123, #42",
432
- TICKET_WARN: "Invalid ID for the configured provider (will be saved anyway)"
462
+ TICKET_WARN: "Invalid ID for the configured provider (will be saved anyway)",
463
+ CONFIRM_DISCARD: "Are you sure you want to discard this recording? All recorded steps will be lost.",
464
+ CONFIRM_DISCARD_BTN: "Yes, discard",
465
+ BACK_BTN: "Go back"
433
466
  },
434
467
  TEST_EDITOR: {
435
468
  NO_TAGS: "No tags",
@@ -511,7 +544,32 @@ var I18N_EN = {
511
544
  ISSUE_TRACKER_SUB: "Add a ticket ID when saving and generate a clickable link in the test list.",
512
545
  ISSUE_TRACKER_PROVIDER: "Provider",
513
546
  ISSUE_TRACKER_BASE_URL: "Base URL",
514
- ISSUE_TRACKER_BASE_URL_PH: "E.g.: https://myproject.atlassian.net"
547
+ ISSUE_TRACKER_BASE_URL_PH: "E.g.: https://myproject.atlassian.net",
548
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
549
+ LOGIN_SETUP_NOT_CONFIGURED: "Not configured",
550
+ LOGIN_SETUP_SET_UP_BTN: "Set up",
551
+ LOGIN_SETUP_TITLE: "Login setup",
552
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Create scaffold",
553
+ LOGIN_SETUP_SELECT_EXISTING: "Select existing file",
554
+ LOGIN_SETUP_FILE_NAME_LABEL: "File name",
555
+ LOGIN_SETUP_FILE_NAME_PH: "E.g.: cypress/common-services/login.service.ts",
556
+ LOGIN_SETUP_RESCAN: "\u{1F504} Re-scan",
557
+ LOGIN_SETUP_FUNCTIONS_DETECTED: "Detected functions",
558
+ LOGIN_SETUP_BLOCK_ASSIGN: "Block assignment",
559
+ LOGIN_SETUP_NO_FUNCTIONS: "No exported functions found. Fill in the scaffold and re-scan.",
560
+ LOGIN_SETUP_BEFORE_LABEL: "before() function (once per suite)",
561
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "beforeEach() function (before every test)",
562
+ LOGIN_SETUP_NONE_OPTION: "\u2014 None \u2014",
563
+ LOGIN_SETUP_SUMMARY: "File: {file} \xB7 before: {before} \xB7 beforeEach: {beforeEach}",
564
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Save",
565
+ LOGIN_SETUP_CLEAR_BTN: "Remove configuration",
566
+ LOGIN_SETUP_CANCEL_BTN: "Cancel",
567
+ LOGIN_SETUP_SOURCE_LABEL: "Login file source",
568
+ LOGIN_SETUP_CREATE_SUB: "Generate a scaffold with sample functions",
569
+ LOGIN_SETUP_SELECT_SUB: "Point to an existing login service file",
570
+ LOGIN_SETUP_INJECT_TITLE: "Add login blocks?",
571
+ LOGIN_SETUP_INJECT_TEXT: "This file does not have the configured login blocks. Do you want to add the import and before/beforeEach blocks now?",
572
+ LOGIN_SETUP_INJECT_CONFIRM: "Yes, add them"
515
573
  },
516
574
  SELECTOR_PICKER: {
517
575
  TITLE: "Select a DOM element",
@@ -602,6 +660,9 @@ var I18N_EN = {
602
660
  FILE_SAVE_ERROR_TOAST: "Error saving the file",
603
661
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures written to cypress/fixtures",
604
662
  FIXTURES_NO_FOLDER_TOAST: "Could not write fixtures (configure the Cypress folder)",
663
+ FILE_UPLOAD_MANUAL_TOAST: "Add {files} to cypress/fixtures/ before running the test.",
664
+ FILE_UPLOAD_COPIED_TOAST: "{files} copied to cypress/fixtures/.",
665
+ FILE_UPLOAD_COPY_ERROR_TOAST: "Could not copy {files} to cypress/fixtures/. Add it manually.",
605
666
  BTN_CONFIG: "Config",
606
667
  BTN_FILES: "Files",
607
668
  BTN_COMMANDS: "Commands",
@@ -649,7 +710,7 @@ var I18N_EN = {
649
710
  SEC_PANELS: "\u{1F5C2} Panels",
650
711
  PANEL_TESTS: "\u{1F4CB} Tests \u2014 saved tests: filter by tag, copy, combine, export.",
651
712
  PANEL_COMMANDS: "\u2328\uFE0F Commands \u2014 current recording: reorder, delete, add assertions.",
652
- PANEL_CONFIG: "\u2699\uFE0F Config \u2014 language, selector, HTTP, Cypress folder, data, visibility, position.",
713
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 language, selector, HTTP, Cypress folder, login setup, data, visibility, position.",
653
714
  PANEL_FILES: "\u{1F4C1} Files \u2014 insert tests into your .cy.ts and launch the runner.",
654
715
  SEC_SELECTOR: "\u{1F50E} Selectors",
655
716
  SELECTOR_1: "Choose the preferred attribute (data-cy, data-testid, aria-label, id) in Config.",
@@ -662,6 +723,7 @@ var I18N_EN = {
662
723
  ADV_RUNNER: "Local runner: npx lib-e2e-cypress-runner to run a spec headless from the editor.",
663
724
  ADV_CROSSAPP: "Cross-app: the recording survives switching micro-frontend (same origin).",
664
725
  ADV_DRAG: "Drag the \u23FA button to move the widget; its position is remembered.",
726
+ ADV_LOGIN: "Login Setup (Ctrl+3): point to your login service; the recorder automatically injects before()/beforeEach() blocks into each new test.",
665
727
  CLOSE: "Close",
666
728
  TAB_REFERENCE: "Quick reference",
667
729
  TAB_GUIDE: "Usage guide",
@@ -679,13 +741,13 @@ var I18N_EN = {
679
741
  G_COV_4: "Reliable selector (data-cy/testid/aria/id) and a picker when none is obvious.",
680
742
  G_COV_5: "Automatic assertions (Alt+click) and the manual builder.",
681
743
  G_COV_6: "Continuity when switching micro-frontend (same origin), invisible mode and i18n.",
744
+ G_COV_7: "Login Setup: configure your login function under Ctrl+3 \u2192 Login Setup; the recorder adds before()/beforeEach() blocks to every new test.",
682
745
  G_SEC_NOTCOVERED: "\u{1F6AB} What it does NOT cover (yet)",
683
746
  G_NC_1: "Different origins / subdomains (same origin only).",
684
747
  G_NC_2: "File upload (selectFile), drag & drop, and hover.",
685
748
  G_NC_3: "The Tab key (Cypress can't .type() it).",
686
749
  G_NC_4: "DELETE requests (intentionally ignored).",
687
- G_NC_5: "Third-party iframes and Shadow DOM may not be captured.",
688
- G_NC_6: "Programmatic login generation (on the roadmap)."
750
+ G_NC_5: "Third-party iframes and Shadow DOM may not be captured."
689
751
  }
690
752
  };
691
753
 
@@ -716,8 +778,11 @@ var I18N_FR = {
716
778
  NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc.",
717
779
  TICKET_LABEL: "Ticket / Issue (optionnel) :",
718
780
  TICKET_PLACEHOLDER: "Ex : PROJ-123, #42",
719
- TICKET_WARN: "ID non valide pour le fournisseur configur\xE9 (sera enregistr\xE9 quand m\xEAme)"
781
+ TICKET_WARN: "ID non valide pour le fournisseur configur\xE9 (sera enregistr\xE9 quand m\xEAme)",
720
782
  // TODO: translate
783
+ CONFIRM_DISCARD: "\xCAtes-vous s\xFBr de vouloir abandonner cet enregistrement ? Toutes les \xE9tapes enregistr\xE9es seront perdues.",
784
+ CONFIRM_DISCARD_BTN: "Oui, abandonner",
785
+ BACK_BTN: "Retour"
721
786
  },
722
787
  TEST_EDITOR: {
723
788
  NO_TAGS: "Sans \xE9tiquettes",
@@ -799,7 +864,32 @@ var I18N_FR = {
799
864
  ISSUE_TRACKER_SUB: "Ajoutez un ID de ticket lors de l'enregistrement et g\xE9n\xE9rez un lien cliquable.",
800
865
  ISSUE_TRACKER_PROVIDER: "Fournisseur",
801
866
  ISSUE_TRACKER_BASE_URL: "URL de base",
802
- ISSUE_TRACKER_BASE_URL_PH: "Ex : https://monprojet.atlassian.net"
867
+ ISSUE_TRACKER_BASE_URL_PH: "Ex : https://monprojet.atlassian.net",
868
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
869
+ LOGIN_SETUP_NOT_CONFIGURED: "Non configur\xE9",
870
+ LOGIN_SETUP_SET_UP_BTN: "Configurer",
871
+ LOGIN_SETUP_TITLE: "Configuration du login",
872
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Cr\xE9er un scaffold",
873
+ LOGIN_SETUP_SELECT_EXISTING: "S\xE9lectionner un fichier existant",
874
+ LOGIN_SETUP_FILE_NAME_LABEL: "Nom du fichier",
875
+ LOGIN_SETUP_FILE_NAME_PH: "Ex : cypress/common-services/login.service.ts",
876
+ LOGIN_SETUP_RESCAN: "\u{1F504} Re-scanner",
877
+ LOGIN_SETUP_FUNCTIONS_DETECTED: "Fonctions d\xE9tect\xE9es",
878
+ LOGIN_SETUP_BLOCK_ASSIGN: "Affectation des blocs",
879
+ LOGIN_SETUP_NO_FUNCTIONS: "Aucune fonction export\xE9e trouv\xE9e. Remplissez le scaffold et re-scannez.",
880
+ LOGIN_SETUP_BEFORE_LABEL: "Fonction before() (une fois par suite)",
881
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "Fonction beforeEach() (avant chaque test)",
882
+ LOGIN_SETUP_NONE_OPTION: "\u2014 Aucune \u2014",
883
+ LOGIN_SETUP_SUMMARY: "Fichier : {file} \xB7 before : {before} \xB7 beforeEach : {beforeEach}",
884
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Enregistrer",
885
+ LOGIN_SETUP_CLEAR_BTN: "Supprimer la configuration",
886
+ LOGIN_SETUP_CANCEL_BTN: "Annuler",
887
+ LOGIN_SETUP_SOURCE_LABEL: "Source du fichier de login",
888
+ LOGIN_SETUP_CREATE_SUB: "G\xE9n\xE9rer un scaffold avec des fonctions exemples",
889
+ LOGIN_SETUP_SELECT_SUB: "Pointer vers un fichier de service de login existant",
890
+ LOGIN_SETUP_INJECT_TITLE: "Ajouter les blocs de login ?",
891
+ LOGIN_SETUP_INJECT_TEXT: "Ce fichier ne contient pas les blocs de login configur\xE9s. Voulez-vous ajouter l'import et les blocs before/beforeEach maintenant ?",
892
+ LOGIN_SETUP_INJECT_CONFIRM: "Oui, ajouter"
803
893
  },
804
894
  SELECTOR_PICKER: {
805
895
  TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
@@ -890,6 +980,12 @@ var I18N_FR = {
890
980
  FILE_SAVE_ERROR_TOAST: "Erreur lors de l'enregistrement",
891
981
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures \xE9crites dans cypress/fixtures",
892
982
  FIXTURES_NO_FOLDER_TOAST: "Impossible d'\xE9crire les fixtures (configure le dossier Cypress)",
983
+ FILE_UPLOAD_MANUAL_TOAST: "Ajoutez {files} \xE0 cypress/fixtures/ avant d'ex\xE9cuter le test.",
984
+ // TODO: translate
985
+ FILE_UPLOAD_COPIED_TOAST: "{files} copi\xE9(s) dans cypress/fixtures/.",
986
+ // TODO: translate
987
+ FILE_UPLOAD_COPY_ERROR_TOAST: "Impossible de copier {files} vers cypress/fixtures/. Ajoutez-le manuellement.",
988
+ // TODO: translate
893
989
  BTN_CONFIG: "Config",
894
990
  BTN_FILES: "Fichiers",
895
991
  BTN_COMMANDS: "Commandes",
@@ -937,7 +1033,7 @@ var I18N_FR = {
937
1033
  SEC_PANELS: "\u{1F5C2} Panneaux",
938
1034
  PANEL_TESTS: "\u{1F4CB} Tests \u2014 enregistr\xE9s : filtrer par tag, copier, combiner, exporter.",
939
1035
  PANEL_COMMANDS: "\u2328\uFE0F Commandes \u2014 enregistrement en cours : r\xE9ordonner, supprimer, ajouter des assertions.",
940
- PANEL_CONFIG: "\u2699\uFE0F Config \u2014 langue, s\xE9lecteur, HTTP, dossier Cypress, donn\xE9es, visibilit\xE9, position.",
1036
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 langue, s\xE9lecteur, HTTP, dossier Cypress, login setup, donn\xE9es, visibilit\xE9, position.",
941
1037
  PANEL_FILES: "\u{1F4C1} Fichiers \u2014 ins\xE8re des tests dans tes .cy.ts et lance le runner.",
942
1038
  SEC_SELECTOR: "\u{1F50E} S\xE9lecteurs",
943
1039
  SELECTOR_1: "Choisis l'attribut pr\xE9f\xE9r\xE9 (data-cy, data-testid, aria-label, id) dans Config.",
@@ -950,6 +1046,8 @@ var I18N_FR = {
950
1046
  ADV_RUNNER: "Runner local : npx lib-e2e-cypress-runner pour lancer un spec headless depuis l'\xE9diteur.",
951
1047
  ADV_CROSSAPP: "Cross-app : l'enregistrement survit au changement de micro-frontend (m\xEAme origine).",
952
1048
  ADV_DRAG: "Fais glisser le bouton \u23FA pour d\xE9placer le widget ; sa position est m\xE9moris\xE9e.",
1049
+ ADV_LOGIN: "Login Setup (Ctrl+3): apunta a tu servicio de login; el grabador inyecta before()/beforeEach() autom\xE1ticamente en cada test nuevo.",
1050
+ // TODO: translate (fr)
953
1051
  CLOSE: "Fermer",
954
1052
  TAB_REFERENCE: "R\xE9f\xE9rence rapide",
955
1053
  TAB_GUIDE: "Guide d'utilisation",
@@ -968,13 +1066,14 @@ var I18N_FR = {
968
1066
  G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
969
1067
  G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
970
1068
  G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
1069
+ G_COV_7: "Login Setup: configura tu funci\xF3n de login en Ctrl+3 \u2192 Login Setup y el grabador a\xF1ade los bloques before()/beforeEach() a cada test nuevo.",
1070
+ // TODO: translate (fr)
971
1071
  G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
972
1072
  G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
973
1073
  G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
974
1074
  G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
975
1075
  G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
976
- G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
977
- G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
1076
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse."
978
1077
  }
979
1078
  };
980
1079
 
@@ -1005,8 +1104,11 @@ var I18N_IT = {
1005
1104
  NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc.",
1006
1105
  TICKET_LABEL: "Ticket / Issue (opzionale):",
1007
1106
  TICKET_PLACEHOLDER: "Es: PROJ-123, #42",
1008
- TICKET_WARN: "ID non valido per il provider configurato (verr\xE0 salvato comunque)"
1107
+ TICKET_WARN: "ID non valido per il provider configurato (verr\xE0 salvato comunque)",
1009
1108
  // TODO: translate
1109
+ CONFIRM_DISCARD: "Sei sicuro di voler scartare questa registrazione? Tutti i passi registrati andranno persi.",
1110
+ CONFIRM_DISCARD_BTN: "S\xEC, scarta",
1111
+ BACK_BTN: "Indietro"
1010
1112
  },
1011
1113
  TEST_EDITOR: {
1012
1114
  NO_TAGS: "Nessuna etichetta",
@@ -1088,7 +1190,32 @@ var I18N_IT = {
1088
1190
  ISSUE_TRACKER_SUB: "Aggiungi un ID ticket al salvataggio e genera un link cliccabile nell'elenco.",
1089
1191
  ISSUE_TRACKER_PROVIDER: "Provider",
1090
1192
  ISSUE_TRACKER_BASE_URL: "URL base",
1091
- ISSUE_TRACKER_BASE_URL_PH: "Es: https://mioprogetto.atlassian.net"
1193
+ ISSUE_TRACKER_BASE_URL_PH: "Es: https://mioprogetto.atlassian.net",
1194
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
1195
+ LOGIN_SETUP_NOT_CONFIGURED: "Non configurato",
1196
+ LOGIN_SETUP_SET_UP_BTN: "Configura",
1197
+ LOGIN_SETUP_TITLE: "Configurazione del login",
1198
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Crea scaffold",
1199
+ LOGIN_SETUP_SELECT_EXISTING: "Seleziona file esistente",
1200
+ LOGIN_SETUP_FILE_NAME_LABEL: "Nome del file",
1201
+ LOGIN_SETUP_FILE_NAME_PH: "Es: cypress/common-services/login.service.ts",
1202
+ LOGIN_SETUP_RESCAN: "\u{1F504} Re-scansiona",
1203
+ LOGIN_SETUP_FUNCTIONS_DETECTED: "Funzioni rilevate",
1204
+ LOGIN_SETUP_BLOCK_ASSIGN: "Assegnazione dei blocchi",
1205
+ LOGIN_SETUP_NO_FUNCTIONS: "Nessuna funzione esportata trovata. Compila lo scaffold e ri-scansiona.",
1206
+ LOGIN_SETUP_BEFORE_LABEL: "Funzione before() (una volta per suite)",
1207
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "Funzione beforeEach() (prima di ogni test)",
1208
+ LOGIN_SETUP_NONE_OPTION: "\u2014 Nessuna \u2014",
1209
+ LOGIN_SETUP_SUMMARY: "File: {file} \xB7 before: {before} \xB7 beforeEach: {beforeEach}",
1210
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Salva",
1211
+ LOGIN_SETUP_CLEAR_BTN: "Rimuovi configurazione",
1212
+ LOGIN_SETUP_CANCEL_BTN: "Annulla",
1213
+ LOGIN_SETUP_SOURCE_LABEL: "Sorgente del file di login",
1214
+ LOGIN_SETUP_CREATE_SUB: "Genera uno scaffold con funzioni di esempio",
1215
+ LOGIN_SETUP_SELECT_SUB: "Punta a un file di servizio di login esistente",
1216
+ LOGIN_SETUP_INJECT_TITLE: "Aggiungere i blocchi di login?",
1217
+ LOGIN_SETUP_INJECT_TEXT: "Questo file non contiene i blocchi di login configurati. Vuoi aggiungere l'import e i blocchi before/beforeEach ora?",
1218
+ LOGIN_SETUP_INJECT_CONFIRM: "S\xEC, aggiungi"
1092
1219
  },
1093
1220
  SELECTOR_PICKER: {
1094
1221
  TITLE: "Seleziona un elemento DOM",
@@ -1179,6 +1306,12 @@ var I18N_IT = {
1179
1306
  FILE_SAVE_ERROR_TOAST: "Errore durante il salvataggio",
1180
1307
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixture scritte in cypress/fixtures",
1181
1308
  FIXTURES_NO_FOLDER_TOAST: "Impossibile scrivere le fixture (configura la cartella Cypress)",
1309
+ FILE_UPLOAD_MANUAL_TOAST: "Aggiungi {files} a cypress/fixtures/ prima di eseguire il test.",
1310
+ // TODO: translate
1311
+ FILE_UPLOAD_COPIED_TOAST: "{files} copiato/i in cypress/fixtures/.",
1312
+ // TODO: translate
1313
+ FILE_UPLOAD_COPY_ERROR_TOAST: "Impossibile copiare {files} in cypress/fixtures/. Aggiungilo manualmente.",
1314
+ // TODO: translate
1182
1315
  BTN_CONFIG: "Config",
1183
1316
  BTN_FILES: "File",
1184
1317
  BTN_COMMANDS: "Comandi",
@@ -1226,7 +1359,7 @@ var I18N_IT = {
1226
1359
  SEC_PANELS: "\u{1F5C2} Pannelli",
1227
1360
  PANEL_TESTS: "\u{1F4CB} Test \u2014 salvati: filtra per tag, copia, combina, esporta.",
1228
1361
  PANEL_COMMANDS: "\u2328\uFE0F Comandi \u2014 registrazione corrente: riordina, elimina, aggiungi asserzioni.",
1229
- PANEL_CONFIG: "\u2699\uFE0F Config \u2014 lingua, selettore, HTTP, cartella Cypress, dati, visibilit\xE0, posizione.",
1362
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 lingua, selettore, HTTP, cartella Cypress, login setup, dati, visibilit\xE0, posizione.",
1230
1363
  PANEL_FILES: "\u{1F4C1} File \u2014 inserisci test nei tuoi .cy.ts e avvia il runner.",
1231
1364
  SEC_SELECTOR: "\u{1F50E} Selettori",
1232
1365
  SELECTOR_1: "Scegli l'attributo preferito (data-cy, data-testid, aria-label, id) in Config.",
@@ -1239,6 +1372,8 @@ var I18N_IT = {
1239
1372
  ADV_RUNNER: "Runner locale: npx lib-e2e-cypress-runner per eseguire uno spec headless dall'editor.",
1240
1373
  ADV_CROSSAPP: "Cross-app: la registrazione sopravvive al cambio di micro-frontend (stessa origine).",
1241
1374
  ADV_DRAG: "Trascina il pulsante \u23FA per spostare il widget; la posizione viene ricordata.",
1375
+ ADV_LOGIN: "Login Setup (Ctrl+3): apunta a tu servicio de login; el grabador inyecta before()/beforeEach() autom\xE1ticamente en cada test nuevo.",
1376
+ // TODO: translate (it)
1242
1377
  CLOSE: "Chiudi",
1243
1378
  TAB_REFERENCE: "Riferimento rapido",
1244
1379
  TAB_GUIDE: "Guida all'uso",
@@ -1257,13 +1392,14 @@ var I18N_IT = {
1257
1392
  G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
1258
1393
  G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
1259
1394
  G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
1395
+ G_COV_7: "Login Setup: configura tu funci\xF3n de login en Ctrl+3 \u2192 Login Setup y el grabador a\xF1ade los bloques before()/beforeEach() a cada test nuevo.",
1396
+ // TODO: translate (it)
1260
1397
  G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
1261
1398
  G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
1262
1399
  G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
1263
1400
  G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
1264
1401
  G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
1265
- G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
1266
- G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
1402
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse."
1267
1403
  }
1268
1404
  };
1269
1405
 
@@ -1294,8 +1430,11 @@ var I18N_DE = {
1294
1430
  NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw.",
1295
1431
  TICKET_LABEL: "Ticket / Issue (optional):",
1296
1432
  TICKET_PLACEHOLDER: "Z. B.: PROJ-123, #42",
1297
- TICKET_WARN: "Ung\xFCltige ID f\xFCr den konfigurierten Anbieter (wird trotzdem gespeichert)"
1433
+ TICKET_WARN: "Ung\xFCltige ID f\xFCr den konfigurierten Anbieter (wird trotzdem gespeichert)",
1298
1434
  // TODO: translate
1435
+ CONFIRM_DISCARD: "Sind Sie sicher, dass Sie diese Aufzeichnung verwerfen m\xF6chten? Alle aufgezeichneten Schritte gehen verloren.",
1436
+ CONFIRM_DISCARD_BTN: "Ja, verwerfen",
1437
+ BACK_BTN: "Zur\xFCck"
1299
1438
  },
1300
1439
  TEST_EDITOR: {
1301
1440
  NO_TAGS: "Keine Tags",
@@ -1377,7 +1516,32 @@ var I18N_DE = {
1377
1516
  ISSUE_TRACKER_SUB: "F\xFCge beim Speichern eine Ticket-ID hinzu und erstelle einen klickbaren Link.",
1378
1517
  ISSUE_TRACKER_PROVIDER: "Anbieter",
1379
1518
  ISSUE_TRACKER_BASE_URL: "Basis-URL",
1380
- ISSUE_TRACKER_BASE_URL_PH: "Z. B.: https://meinprojekt.atlassian.net"
1519
+ ISSUE_TRACKER_BASE_URL_PH: "Z. B.: https://meinprojekt.atlassian.net",
1520
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
1521
+ LOGIN_SETUP_NOT_CONFIGURED: "Nicht konfiguriert",
1522
+ LOGIN_SETUP_SET_UP_BTN: "Einrichten",
1523
+ LOGIN_SETUP_TITLE: "Login-Konfiguration",
1524
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Scaffold erstellen",
1525
+ LOGIN_SETUP_SELECT_EXISTING: "Vorhandene Datei ausw\xE4hlen",
1526
+ LOGIN_SETUP_FILE_NAME_LABEL: "Dateiname",
1527
+ LOGIN_SETUP_FILE_NAME_PH: "Z. B.: cypress/common-services/login.service.ts",
1528
+ LOGIN_SETUP_RESCAN: "\u{1F504} Erneut scannen",
1529
+ LOGIN_SETUP_FUNCTIONS_DETECTED: "Erkannte Funktionen",
1530
+ LOGIN_SETUP_BLOCK_ASSIGN: "Blockzuweisung",
1531
+ LOGIN_SETUP_NO_FUNCTIONS: "Keine exportierten Funktionen gefunden. Scaffold ausf\xFCllen und erneut scannen.",
1532
+ LOGIN_SETUP_BEFORE_LABEL: "before()-Funktion (einmal pro Suite)",
1533
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "beforeEach()-Funktion (vor jedem Test)",
1534
+ LOGIN_SETUP_NONE_OPTION: "\u2014 Keine \u2014",
1535
+ LOGIN_SETUP_SUMMARY: "Datei: {file} \xB7 before: {before} \xB7 beforeEach: {beforeEach}",
1536
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Speichern",
1537
+ LOGIN_SETUP_CLEAR_BTN: "Konfiguration entfernen",
1538
+ LOGIN_SETUP_CANCEL_BTN: "Abbrechen",
1539
+ LOGIN_SETUP_SOURCE_LABEL: "Quelle der Login-Datei",
1540
+ LOGIN_SETUP_CREATE_SUB: "Scaffold mit Beispielfunktionen erstellen",
1541
+ LOGIN_SETUP_SELECT_SUB: "Auf eine vorhandene Login-Service-Datei verweisen",
1542
+ LOGIN_SETUP_INJECT_TITLE: "Login-Bl\xF6cke hinzuf\xFCgen?",
1543
+ LOGIN_SETUP_INJECT_TEXT: "Diese Datei enth\xE4lt die konfigurierten Login-Bl\xF6cke nicht. M\xF6chten Sie jetzt den Import und die before/beforeEach-Bl\xF6cke hinzuf\xFCgen?",
1544
+ LOGIN_SETUP_INJECT_CONFIRM: "Ja, hinzuf\xFCgen"
1381
1545
  },
1382
1546
  SELECTOR_PICKER: {
1383
1547
  TITLE: "DOM-Element ausw\xE4hlen",
@@ -1468,6 +1632,12 @@ var I18N_DE = {
1468
1632
  FILE_SAVE_ERROR_TOAST: "Fehler beim Speichern",
1469
1633
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures in cypress/fixtures geschrieben",
1470
1634
  FIXTURES_NO_FOLDER_TOAST: "Fixtures konnten nicht geschrieben werden (Cypress-Ordner konfigurieren)",
1635
+ FILE_UPLOAD_MANUAL_TOAST: "F\xFCge {files} zu cypress/fixtures/ hinzu, bevor du den Test ausf\xFChrst.",
1636
+ // TODO: translate
1637
+ FILE_UPLOAD_COPIED_TOAST: "{files} nach cypress/fixtures/ kopiert.",
1638
+ // TODO: translate
1639
+ FILE_UPLOAD_COPY_ERROR_TOAST: "{files} konnte nicht nach cypress/fixtures/ kopiert werden. F\xFCge es manuell hinzu.",
1640
+ // TODO: translate
1471
1641
  BTN_CONFIG: "Config",
1472
1642
  BTN_FILES: "Dateien",
1473
1643
  BTN_COMMANDS: "Befehle",
@@ -1515,7 +1685,7 @@ var I18N_DE = {
1515
1685
  SEC_PANELS: "\u{1F5C2} Panels",
1516
1686
  PANEL_TESTS: "\u{1F4CB} Tests \u2014 gespeichert: nach Tag filtern, kopieren, kombinieren, exportieren.",
1517
1687
  PANEL_COMMANDS: "\u2328\uFE0F Befehle \u2014 aktuelle Aufzeichnung: umsortieren, l\xF6schen, Assertions hinzuf\xFCgen.",
1518
- PANEL_CONFIG: "\u2699\uFE0F Config \u2014 Sprache, Selektor, HTTP, Cypress-Ordner, Daten, Sichtbarkeit, Position.",
1688
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 Sprache, Selektor, HTTP, Cypress-Ordner, Login-Setup, Daten, Sichtbarkeit, Position.",
1519
1689
  PANEL_FILES: "\u{1F4C1} Dateien \u2014 Tests in deine .cy.ts einf\xFCgen und den Runner starten.",
1520
1690
  SEC_SELECTOR: "\u{1F50E} Selektoren",
1521
1691
  SELECTOR_1: "W\xE4hle das bevorzugte Attribut (data-cy, data-testid, aria-label, id) unter Config.",
@@ -1528,6 +1698,8 @@ var I18N_DE = {
1528
1698
  ADV_RUNNER: "Lokaler Runner: npx lib-e2e-cypress-runner, um einen Spec headless aus dem Editor zu starten.",
1529
1699
  ADV_CROSSAPP: "Cross-App: die Aufzeichnung \xFCbersteht den Wechsel des Micro-Frontends (gleicher Origin).",
1530
1700
  ADV_DRAG: "Ziehe den \u23FA-Button, um das Widget zu verschieben; die Position wird gemerkt.",
1701
+ ADV_LOGIN: "Login Setup (Ctrl+3): apunta a tu servicio de login; el grabador inyecta before()/beforeEach() autom\xE1ticamente en cada test nuevo.",
1702
+ // TODO: translate (de)
1531
1703
  CLOSE: "Schlie\xDFen",
1532
1704
  TAB_REFERENCE: "Kurzreferenz",
1533
1705
  TAB_GUIDE: "Nutzungsleitfaden",
@@ -1546,13 +1718,14 @@ var I18N_DE = {
1546
1718
  G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
1547
1719
  G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
1548
1720
  G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
1721
+ G_COV_7: "Login Setup: configura tu funci\xF3n de login en Ctrl+3 \u2192 Login Setup y el grabador a\xF1ade los bloques before()/beforeEach() a cada test nuevo.",
1722
+ // TODO: translate (de)
1549
1723
  G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
1550
1724
  G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
1551
1725
  G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
1552
1726
  G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
1553
1727
  G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
1554
- G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
1555
- G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
1728
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse."
1556
1729
  }
1557
1730
  };
1558
1731
 
@@ -1595,33 +1768,6 @@ var TranslationService = class {
1595
1768
  };
1596
1769
  var translationService = new TranslationService();
1597
1770
 
1598
- // src/utils/code-format.utils.ts
1599
- function gcd(a, b) {
1600
- while (b) {
1601
- const t = b;
1602
- b = a % b;
1603
- a = t;
1604
- }
1605
- return a;
1606
- }
1607
- function escapeSingleQuotes(value) {
1608
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1609
- }
1610
- function normalizeBlock(code, baseIndent) {
1611
- const lines = code.split("\n").map((l) => l.trimEnd());
1612
- const nonEmpty = lines.filter((l) => l.trim().length > 0);
1613
- if (!nonEmpty.length) return "";
1614
- const indents = nonEmpty.map((l) => l.length - l.trimStart().length);
1615
- const minIndent = Math.min(...indents);
1616
- const relIndents = [...new Set(indents.map((n) => n - minIndent).filter((n) => n > 0))];
1617
- const step = relIndents.length > 0 ? relIndents.reduce(gcd) : 2;
1618
- return lines.map((l) => {
1619
- if (!l.trim()) return "";
1620
- const level = Math.round((l.length - l.trimStart().length - minIndent) / step);
1621
- return baseIndent + " ".repeat(level) + l.trimStart();
1622
- }).join("\n");
1623
- }
1624
-
1625
1771
  // src/services/transformation.service.ts
1626
1772
  var TransformationService = class {
1627
1773
  toLang(lang) {
@@ -1686,7 +1832,8 @@ var advancedTestTransformationService = new AdvancedTestTransformationService();
1686
1832
  // src/utils/assertion.utils.ts
1687
1833
  var MAX_TEXT_LENGTH = 60;
1688
1834
  function inferAssertionCommand(el, selector) {
1689
- const should = (body) => `cy.get('${selector}').should(${body})`;
1835
+ const safeSelector = escapeSingleQuotes(selector);
1836
+ const should = (body) => `cy.get('${safeSelector}').should(${body})`;
1690
1837
  const tag = el.tagName.toLowerCase();
1691
1838
  if (tag === "input") {
1692
1839
  const input = el;
@@ -1723,6 +1870,7 @@ var RecordingService = class {
1723
1870
  selectorNotFound$ = new Subject(null);
1724
1871
  inputDebounceTimers = /* @__PURE__ */ new Map();
1725
1872
  fixtures = /* @__PURE__ */ new Map();
1873
+ uploadedFiles = /* @__PURE__ */ new Map();
1726
1874
  abort = new AbortController();
1727
1875
  selectorStrategy = "data-cy";
1728
1876
  /** Stable id of the current live session (null when none has started). */
@@ -1749,7 +1897,7 @@ var RecordingService = class {
1749
1897
  this.isPaused$.next(false);
1750
1898
  this.isRecording$.next(true);
1751
1899
  this.addCommand(`cy.viewport(1900, 1200)`);
1752
- this.addCommand(`cy.visit('${window.location.pathname}')`);
1900
+ this.addCommand(`cy.visit('${escapeSingleQuotes(window.location.pathname)}')`);
1753
1901
  this.addCommand(`cy.get('[data-cy="lib-e2e-cypress-for-dummys"]').invoke('hide');`);
1754
1902
  }
1755
1903
  /**
@@ -1832,8 +1980,8 @@ var RecordingService = class {
1832
1980
  }
1833
1981
  registerInterceptor(method, url, alias, fixtureFile) {
1834
1982
  if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
1835
- const wildcard = this.urlToWildcard(url, method);
1836
- const command = fixtureFile ? `cy.intercept('${method}', '${wildcard}', { fixture: '${fixtureFile}' }).as('${alias}')` : `cy.intercept('${method}', '${wildcard}').as('${alias}')`;
1983
+ const wildcard = escapeSingleQuotes(this.urlToWildcard(url, method));
1984
+ const command = fixtureFile ? `cy.intercept('${method}', '${wildcard}', { fixture: '${escapeSingleQuotes(fixtureFile)}' }).as('${alias}')` : `cy.intercept('${method}', '${wildcard}').as('${alias}')`;
1837
1985
  const current = this.interceptors$.getValue();
1838
1986
  if (!current.includes(command)) {
1839
1987
  this.interceptors$.next([...current, command]);
@@ -1847,10 +1995,20 @@ var RecordingService = class {
1847
1995
  getFixturesSnapshot() {
1848
1996
  return [...this.fixtures.entries()].map(([name, content]) => ({ name, content }));
1849
1997
  }
1998
+ addUploadedFile(filename, bytes) {
1999
+ this.uploadedFiles.set(filename, bytes);
2000
+ }
2001
+ getUploadedFilesSnapshot() {
2002
+ return [...this.uploadedFiles.entries()].map(([filename, bytes]) => ({ filename, bytes }));
2003
+ }
2004
+ clearUploadedFiles() {
2005
+ this.uploadedFiles.clear();
2006
+ }
1850
2007
  clearCommands() {
1851
2008
  this.commands$.next([]);
1852
2009
  this.interceptors$.next([]);
1853
2010
  this.fixtures.clear();
2011
+ this.uploadedFiles.clear();
1854
2012
  }
1855
2013
  clearInterceptors() {
1856
2014
  this.interceptors$.next([]);
@@ -1970,7 +2128,8 @@ var RecordingService = class {
1970
2128
  if (e.altKey) return;
1971
2129
  const selector = this.resolveSelectorFor(e.target);
1972
2130
  if (!selector) return;
1973
- const clickCmd = `cy.get('${selector}').click()`;
2131
+ const safeSelector = escapeSingleQuotes(selector);
2132
+ const clickCmd = `cy.get('${safeSelector}').click()`;
1974
2133
  let cmds = this.commands$.getValue();
1975
2134
  let removed = 0;
1976
2135
  while (removed < 2 && cmds.length > 0 && cmds[cmds.length - 1] === clickCmd) {
@@ -1978,7 +2137,7 @@ var RecordingService = class {
1978
2137
  removed++;
1979
2138
  }
1980
2139
  if (removed > 0) this.commands$.next(cmds);
1981
- this.addCommand(`cy.get('${selector}').dblclick()`);
2140
+ this.addCommand(`cy.get('${safeSelector}').dblclick()`);
1982
2141
  },
1983
2142
  { signal: this.abort.signal }
1984
2143
  );
@@ -1990,7 +2149,7 @@ var RecordingService = class {
1990
2149
  if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
1991
2150
  const selector = this.resolveSelectorFor(e.target);
1992
2151
  if (!selector) return;
1993
- this.addCommand(`cy.get('${selector}').rightclick()`);
2152
+ this.addCommand(`cy.get('${escapeSingleQuotes(selector)}').rightclick()`);
1994
2153
  },
1995
2154
  { signal: this.abort.signal }
1996
2155
  );
@@ -2010,7 +2169,7 @@ var RecordingService = class {
2010
2169
  if (!selector) return;
2011
2170
  this.flushInputDebounce(target);
2012
2171
  const token = key === "Enter" ? "{enter}" : "{esc}";
2013
- this.addCommand(`cy.get('${selector}').type('${token}')`);
2172
+ this.addCommand(`cy.get('${escapeSingleQuotes(selector)}').type('${token}')`);
2014
2173
  },
2015
2174
  { signal: this.abort.signal }
2016
2175
  );
@@ -2034,19 +2193,52 @@ var RecordingService = class {
2034
2193
  if (!this.isRecording$.getValue()) return;
2035
2194
  const target = e.target;
2036
2195
  if (!target || this.isOwnElement(target)) return;
2037
- if (target.tagName.toLowerCase() === "select") {
2196
+ const tag = target.tagName.toLowerCase();
2197
+ if (tag === "select") {
2038
2198
  this.handleSelectEvent(target);
2199
+ } else if (tag === "input") {
2200
+ const type = (target.getAttribute("type") ?? "").toLowerCase();
2201
+ if (type === "file") {
2202
+ void this.handleFileInputChange(target);
2203
+ }
2039
2204
  }
2040
2205
  },
2041
2206
  { signal: this.abort.signal }
2042
2207
  );
2043
2208
  }
2209
+ async handleFileInputChange(target) {
2210
+ if (this.isPaused$.getValue()) return;
2211
+ const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2212
+ if (!container) return;
2213
+ const selector = this.getReliableSelector(container);
2214
+ if (!selector || this.isOwnSelector(selector)) return;
2215
+ const files = Array.from(target.files ?? []);
2216
+ if (!files.length) return;
2217
+ const paths = files.map((f) => `'cypress/fixtures/${escapeSingleQuotes(f.name)}'`);
2218
+ const pathArg = paths.length === 1 ? paths[0] : `[${paths.join(", ")}]`;
2219
+ this.addCommand(`cy.get('${escapeSingleQuotes(selector)}').selectFile(${pathArg})`);
2220
+ for (const file of files) {
2221
+ const bytes = await this.readFileAsArrayBuffer(file);
2222
+ this.addUploadedFile(file.name, bytes);
2223
+ }
2224
+ }
2225
+ readFileAsArrayBuffer(file) {
2226
+ if (typeof file.arrayBuffer === "function") {
2227
+ return file.arrayBuffer();
2228
+ }
2229
+ return new Promise((resolve, reject) => {
2230
+ const reader = new FileReader();
2231
+ reader.onload = () => resolve(reader.result);
2232
+ reader.onerror = () => reject(reader.error);
2233
+ reader.readAsArrayBuffer(file);
2234
+ });
2235
+ }
2044
2236
  listenToRouteChanges() {
2045
2237
  let lastUrl = window.location.pathname + window.location.search + window.location.hash;
2046
2238
  const addUrlCommand = (newUrl) => {
2047
2239
  if (!this.isRecording$.getValue()) return;
2048
2240
  if (newUrl === lastUrl) return;
2049
- this.addCommand(`cy.url().should('include', '${newUrl}')`);
2241
+ this.addCommand(`cy.url().should('include', '${escapeSingleQuotes(newUrl)}')`);
2050
2242
  lastUrl = newUrl;
2051
2243
  };
2052
2244
  const wrapMethod = (type) => {
@@ -2147,7 +2339,8 @@ var RecordingService = class {
2147
2339
  if (matSelect) dataCy = matSelect.closest("[data-cy]")?.getAttribute("data-cy") ?? null;
2148
2340
  if (!dataCy) dataCy = target.closest("[data-cy]")?.getAttribute("data-cy") ?? null;
2149
2341
  if (dataCy) {
2150
- this.addCommand(`cy.get('[data-cy="${dataCy}"]').click()`);
2342
+ const safeSel = escapeSingleQuotes(`[data-cy="${escapeCssAttrValue(dataCy)}"]`);
2343
+ this.addCommand(`cy.get('${safeSel}').click()`);
2151
2344
  return;
2152
2345
  }
2153
2346
  if (selector) {
@@ -2179,7 +2372,7 @@ var RecordingService = class {
2179
2372
  const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2180
2373
  if (!container) return;
2181
2374
  const selector = this.getReliableSelector(container);
2182
- const value = target.value.replace(/'/g, "\\'");
2375
+ const value = escapeSingleQuotes(target.value);
2183
2376
  this.addGenericCommand({
2184
2377
  selector,
2185
2378
  action: (s) => `cy.get('${s}').clear().type('${value}')`
@@ -2198,7 +2391,7 @@ var RecordingService = class {
2198
2391
  const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2199
2392
  if (!container) return;
2200
2393
  const selector = this.getReliableSelector(container);
2201
- const value = target.value.replace(/'/g, "\\'");
2394
+ const value = escapeSingleQuotes(target.value);
2202
2395
  this.addGenericCommand({
2203
2396
  selector,
2204
2397
  action: (s) => `cy.get('${s}').select('${value}')`
@@ -2207,7 +2400,7 @@ var RecordingService = class {
2207
2400
  // ── Shared helpers ────────────────────────────────────────────────────────
2208
2401
  addGenericCommand(opts) {
2209
2402
  if (!opts.selector || this.isOwnSelector(opts.selector)) return;
2210
- this.addCommand(opts.action(opts.selector));
2403
+ this.addCommand(opts.action(escapeSingleQuotes(opts.selector)));
2211
2404
  }
2212
2405
  getReliableSelector(el) {
2213
2406
  const strategy = this.selectorStrategy;
@@ -2219,20 +2412,20 @@ var RecordingService = class {
2219
2412
  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;
2220
2413
  switch (strategy) {
2221
2414
  case "data-testid":
2222
- if (dataTestid) return `[data-testid="${dataTestid}"]`;
2223
- if (dataCy) return `[data-cy="${dataCy}"]`;
2415
+ if (dataTestid) return `[data-testid="${escapeCssAttrValue(dataTestid)}"]`;
2416
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
2224
2417
  return validId ? `#${validId}` : null;
2225
2418
  case "aria-label":
2226
- if (ariaLabel) return `[aria-label="${ariaLabel}"]`;
2227
- if (dataCy) return `[data-cy="${dataCy}"]`;
2419
+ if (ariaLabel) return `[aria-label="${escapeCssAttrValue(ariaLabel)}"]`;
2420
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
2228
2421
  return validId ? `#${validId}` : null;
2229
2422
  case "id":
2230
2423
  if (validId) return `#${validId}`;
2231
- if (dataCy) return `[data-cy="${dataCy}"]`;
2232
- return dataTestid ? `[data-testid="${dataTestid}"]` : null;
2424
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
2425
+ return dataTestid ? `[data-testid="${escapeCssAttrValue(dataTestid)}"]` : null;
2233
2426
  default:
2234
- if (dataCy) return `[data-cy="${dataCy}"]`;
2235
- if (dataDotCy) return `[data.cy="${dataDotCy}"]`;
2427
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
2428
+ if (dataDotCy) return `[data.cy="${escapeCssAttrValue(dataDotCy)}"]`;
2236
2429
  return validId ? `#${validId}` : null;
2237
2430
  }
2238
2431
  }
@@ -2258,8 +2451,40 @@ var RecordingService = class {
2258
2451
 
2259
2452
  // src/services/persistence.service.ts
2260
2453
  import { openDB } from "idb";
2454
+
2455
+ // src/models/issue-tracker.model.ts
2456
+ var ISSUE_TRACKER_PRESETS = {
2457
+ jira: { id: "jira", label: "Jira", urlPattern: "{baseUrl}/browse/{id}", idPattern: /^[A-Z][A-Z0-9]+-\d+$/ },
2458
+ github: { id: "github", label: "GitHub Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
2459
+ gitlab: { id: "gitlab", label: "GitLab Issues", urlPattern: "{baseUrl}/-/issues/{id}", idPattern: /^\d+$/ },
2460
+ azure: { id: "azure", label: "Azure DevOps", urlPattern: "{baseUrl}/_workitems/edit/{id}", idPattern: /^\d+$/ },
2461
+ bitbucket: { id: "bitbucket", label: "Bitbucket Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
2462
+ custom: { id: "custom", label: "Custom", urlPattern: "{baseUrl}/{id}", idPattern: /.+/ }
2463
+ };
2464
+ var DEFAULT_ISSUE_TRACKER_CONFIG = {
2465
+ enabled: false,
2466
+ provider: "jira",
2467
+ baseUrl: ""
2468
+ };
2469
+
2470
+ // src/utils/ticket.utils.ts
2471
+ function buildTicketUrl(ticketId, config) {
2472
+ if (!config.enabled || !ticketId.trim() || !config.baseUrl.trim()) return null;
2473
+ const preset = ISSUE_TRACKER_PRESETS[config.provider];
2474
+ const base = config.baseUrl.replace(/\/+$/, "");
2475
+ return preset.urlPattern.replace("{baseUrl}", base).replace("{id}", encodeURIComponent(ticketId.trim()));
2476
+ }
2477
+ function buildTicketComment(ticketId) {
2478
+ return `// Ticket: ${ticketId.trim()}`;
2479
+ }
2480
+ function isValidTicketId(ticketId, provider) {
2481
+ if (!ticketId.trim()) return false;
2482
+ return ISSUE_TRACKER_PRESETS[provider].idPattern.test(ticketId.trim());
2483
+ }
2484
+
2485
+ // src/services/persistence.service.ts
2261
2486
  var ACTIVE_SESSION_ID = 1;
2262
- var PersistenceService = class {
2487
+ var PersistenceService = class _PersistenceService {
2263
2488
  // dbName is overridable so tests can use unique names for isolation.
2264
2489
  constructor(dbName = DB_SCHEMA.name) {
2265
2490
  this.dbName = dbName;
@@ -2312,7 +2537,8 @@ var PersistenceService = class {
2312
2537
  if (!record) return null;
2313
2538
  const commands = await this.getCommandStrings(testId);
2314
2539
  const interceptors = await this.getInterceptorStrings(testId);
2315
- const itBlock = `it('${escapeSingleQuotes(record.name)}', () => {
2540
+ const ticketComment = record.ticketId ? buildTicketComment(record.ticketId) + "\n" : "";
2541
+ const itBlock = `${ticketComment}it('${escapeSingleQuotes(record.name)}', () => {
2316
2542
  ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2317
2543
  });`;
2318
2544
  const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
@@ -2393,6 +2619,30 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2393
2619
  const records = await db.getAll("configuration");
2394
2620
  return records[0] ?? null;
2395
2621
  }
2622
+ // ── Login setup config ───────────────────────────────────────────────────
2623
+ static LOGIN_SETUP_KEY = "loginSetupConfig";
2624
+ async saveLoginSetup(config) {
2625
+ await this.setConfigKey(_PersistenceService.LOGIN_SETUP_KEY, JSON.stringify(config));
2626
+ }
2627
+ async getLoginSetup() {
2628
+ const record = await this.getConfig(_PersistenceService.LOGIN_SETUP_KEY);
2629
+ if (!record) return null;
2630
+ const raw = record[_PersistenceService.LOGIN_SETUP_KEY];
2631
+ if (typeof raw !== "string") return null;
2632
+ try {
2633
+ return JSON.parse(raw);
2634
+ } catch {
2635
+ return null;
2636
+ }
2637
+ }
2638
+ async clearLoginSetup() {
2639
+ const db = await this.getDB();
2640
+ const records = await db.getAll("configuration");
2641
+ if (!records.length) return;
2642
+ const current = { ...records[0] };
2643
+ delete current[_PersistenceService.LOGIN_SETUP_KEY];
2644
+ await db.put("configuration", current);
2645
+ }
2396
2646
  // ── Active recording session ──────────────────────────────────────────────
2397
2647
  /** Upserts the live recording session (single record, fixed key). */
2398
2648
  async saveActiveSession(state) {
@@ -2435,6 +2685,24 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2435
2685
  }
2436
2686
  await this.bulkInsertWithoutId("interceptors", interceptors);
2437
2687
  }
2688
+ async hasDirectoryAccess() {
2689
+ const config = await this.getGeneralConfig();
2690
+ return !!config?.["cypressDirectoryHandle"];
2691
+ }
2692
+ async writeUploadedFile(filename, bytes) {
2693
+ const config = await this.getGeneralConfig();
2694
+ const dirHandle = config?.["cypressDirectoryHandle"];
2695
+ if (!dirHandle) throw new Error("No Cypress folder configured");
2696
+ const perm = dirHandle;
2697
+ let state = await perm.queryPermission({ mode: "readwrite" });
2698
+ if (state !== "granted") state = await perm.requestPermission({ mode: "readwrite" });
2699
+ if (state !== "granted") throw new Error("Write permission denied");
2700
+ const fixturesDir = await dirHandle.getDirectoryHandle("fixtures", { create: true });
2701
+ const fileHandle = await fixturesDir.getFileHandle(filename, { create: true });
2702
+ const writable = await fileHandle.createWritable();
2703
+ await writable.write(bytes);
2704
+ await writable.close();
2705
+ }
2438
2706
  async requestDirectoryPermissions() {
2439
2707
  if (!("showDirectoryPicker" in window)) {
2440
2708
  throw new Error("File System Access API not supported");
@@ -2477,7 +2745,54 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2477
2745
  }
2478
2746
  }
2479
2747
  };
2480
- var persistenceService = new PersistenceService();
2748
+
2749
+ // src/utils/redact.utils.ts
2750
+ var SENSITIVE_SUBSTRINGS = [
2751
+ "password",
2752
+ "passwd",
2753
+ "passphrase",
2754
+ "secret",
2755
+ "token",
2756
+ // access_token, refresh_token, id_token, csrf_token…
2757
+ "apikey",
2758
+ // api_key, x-api-key — NOT bare "key" (avoids "normalKey")
2759
+ "authorization",
2760
+ "cookie",
2761
+ "credential",
2762
+ "privatekey",
2763
+ "sessionid",
2764
+ "creditcard",
2765
+ "cardnumber",
2766
+ "cvv"
2767
+ ];
2768
+ var SENSITIVE_EXACT = /* @__PURE__ */ new Set([
2769
+ "auth",
2770
+ "pwd",
2771
+ "pin",
2772
+ "otp",
2773
+ "ssn"
2774
+ ]);
2775
+ function normalizeKey(key) {
2776
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
2777
+ }
2778
+ function isSensitiveKey(key) {
2779
+ const normalized = normalizeKey(key);
2780
+ if (SENSITIVE_EXACT.has(normalized)) return true;
2781
+ return SENSITIVE_SUBSTRINGS.some((s) => normalized.includes(s));
2782
+ }
2783
+ function redactSensitiveFields(obj) {
2784
+ if (Array.isArray(obj)) {
2785
+ return obj.map(redactSensitiveFields);
2786
+ }
2787
+ if (obj !== null && typeof obj === "object") {
2788
+ const result = {};
2789
+ for (const [key, value] of Object.entries(obj)) {
2790
+ result[key] = isSensitiveKey(key) ? "[REDACTED]" : redactSensitiveFields(value);
2791
+ }
2792
+ return result;
2793
+ }
2794
+ return obj;
2795
+ }
2481
2796
 
2482
2797
  // src/services/http-monitor.ts
2483
2798
  var INTERCEPTED_METHODS = ["GET", "POST", "PUT"];
@@ -2490,8 +2805,12 @@ function generateAlias(method, url) {
2490
2805
  return `${method.toLowerCase()}-intercepted-request`;
2491
2806
  }
2492
2807
  }
2808
+ var SAFE_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
2493
2809
  function buildValidations(base, obj) {
2494
- return Object.keys(obj).filter((key) => key !== "id" && key !== "uid").map((key) => `expect(${base}.${key}).to.equal(${JSON.stringify(obj[key])});`).join("\n");
2810
+ return Object.keys(obj).filter((key) => key !== "id" && key !== "uid").map((key) => {
2811
+ const accessor = SAFE_IDENTIFIER.test(key) ? `.${key}` : `['${escapeSingleQuotes(key)}']`;
2812
+ return `expect(${base}${accessor}).to.equal(${JSON.stringify(obj[key])});`;
2813
+ }).join("\n");
2495
2814
  }
2496
2815
  function buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody) {
2497
2816
  if (extendedHttp && method === "GET" && responseBody) {
@@ -2543,20 +2862,143 @@ function resolveMethod(input, init) {
2543
2862
  if (input instanceof Request) return input.method.toUpperCase();
2544
2863
  return "GET";
2545
2864
  }
2865
+ async function _handleFetchInterception(recording, input, init, responseClone) {
2866
+ const method = resolveMethod(input, init);
2867
+ if (!INTERCEPTED_METHODS.includes(method)) return;
2868
+ const url = resolveUrl(input);
2869
+ const alias = generateAlias(method, url);
2870
+ recording.registerInterceptor(method, url, alias);
2871
+ const extendedHttp = localStorage.getItem("extendedHttpCommands") === "true";
2872
+ const fixtureMode = localStorage.getItem("fixtureMode") === "true";
2873
+ const requestBody = extendedHttp ? parseRequestBody(init) : null;
2874
+ let responseText = null;
2875
+ let responseBody = null;
2876
+ if (extendedHttp || fixtureMode && method === "GET") {
2877
+ try {
2878
+ responseText = await responseClone.text();
2879
+ if (extendedHttp) responseBody = parseJsonObject(responseText);
2880
+ } catch {
2881
+ }
2882
+ }
2883
+ if (fixtureMode && method === "GET" && responseText !== null) {
2884
+ const pretty = prettyJsonOrNull(responseText);
2885
+ if (pretty !== null) {
2886
+ const redacted = redactSensitiveFields(JSON.parse(pretty));
2887
+ recording.registerFixture(`${alias}.json`, JSON.stringify(redacted, null, 2));
2888
+ }
2889
+ }
2890
+ const cmd = buildCyWaitCommand(
2891
+ method,
2892
+ alias,
2893
+ extendedHttp,
2894
+ requestBody ? redactSensitiveFields(requestBody) : null,
2895
+ responseBody ? redactSensitiveFields(responseBody) : null
2896
+ );
2897
+ recording.addCommand(cmd);
2898
+ }
2899
+ function _handleXhrInterception(recording, method, url, requestBody, responseText) {
2900
+ if (!INTERCEPTED_METHODS.includes(method)) return;
2901
+ const alias = generateAlias(method, url);
2902
+ recording.registerInterceptor(method, url, alias);
2903
+ if (localStorage.getItem("fixtureMode") === "true" && method === "GET") {
2904
+ const pretty = prettyJsonOrNull(responseText);
2905
+ if (pretty !== null) {
2906
+ const redacted = redactSensitiveFields(JSON.parse(pretty));
2907
+ recording.registerFixture(`${alias}.json`, JSON.stringify(redacted, null, 2));
2908
+ }
2909
+ }
2910
+ const extendedHttp = localStorage.getItem("extendedHttpCommands") === "true";
2911
+ const responseBody = extendedHttp ? parseJsonObject(responseText) : null;
2912
+ const cmd = buildCyWaitCommand(
2913
+ method,
2914
+ alias,
2915
+ extendedHttp,
2916
+ requestBody ? redactSensitiveFields(requestBody) : null,
2917
+ responseBody ? redactSensitiveFields(responseBody) : null
2918
+ );
2919
+ recording.addCommand(cmd);
2920
+ }
2921
+ var _refCount = 0;
2922
+ var _originalFetch = null;
2923
+ var _originalXHR = null;
2924
+ var _recordings = /* @__PURE__ */ new Set();
2925
+ function _patchFetch() {
2926
+ const orig = window.fetch;
2927
+ _originalFetch = orig;
2928
+ window.fetch = async (input, init) => {
2929
+ const response = await orig(input, init);
2930
+ try {
2931
+ const active = [..._recordings];
2932
+ if (active.length > 0) {
2933
+ await Promise.all(
2934
+ active.map((rec) => _handleFetchInterception(rec, input, init, response.clone()).catch(() => {
2935
+ }))
2936
+ );
2937
+ }
2938
+ } catch {
2939
+ }
2940
+ return response;
2941
+ };
2942
+ }
2943
+ function _restoreFetch() {
2944
+ if (!_originalFetch) return;
2945
+ window.fetch = _originalFetch;
2946
+ _originalFetch = null;
2947
+ }
2948
+ function _patchXhr() {
2949
+ const OrigXHR = window.XMLHttpRequest;
2950
+ _originalXHR = OrigXHR;
2951
+ window.XMLHttpRequest = class extends OrigXHR {
2952
+ _xhrMethod = "GET";
2953
+ _xhrUrl = "";
2954
+ _xhrRequestBody = null;
2955
+ open(method, url, async = true, user, password) {
2956
+ this._xhrMethod = method.toUpperCase();
2957
+ this._xhrUrl = url instanceof URL ? url.href : url;
2958
+ super.open(method, url, async, user, password);
2959
+ }
2960
+ send(body) {
2961
+ if (typeof body === "string") {
2962
+ this._xhrRequestBody = parseJsonObject(body);
2963
+ }
2964
+ this.addEventListener("load", () => {
2965
+ try {
2966
+ for (const rec of _recordings) {
2967
+ _handleXhrInterception(rec, this._xhrMethod, this._xhrUrl, this._xhrRequestBody, this.responseText);
2968
+ }
2969
+ } catch {
2970
+ }
2971
+ });
2972
+ super.send(body);
2973
+ }
2974
+ };
2975
+ }
2976
+ function _restoreXhr() {
2977
+ if (!_originalXHR) return;
2978
+ window.XMLHttpRequest = _originalXHR;
2979
+ _originalXHR = null;
2980
+ }
2546
2981
  var HttpMonitor = class {
2547
2982
  constructor(recording) {
2548
2983
  this.recording = recording;
2549
2984
  }
2550
2985
  recording;
2551
- originalFetch = null;
2552
- originalXHR = null;
2553
2986
  install() {
2554
- this.installFetch();
2555
- this.installXhr();
2987
+ _recordings.add(this.recording);
2988
+ _refCount++;
2989
+ if (_refCount === 1) {
2990
+ _patchFetch();
2991
+ _patchXhr();
2992
+ }
2556
2993
  }
2557
2994
  uninstall() {
2558
- this.uninstallFetch();
2559
- this.uninstallXhr();
2995
+ _recordings.delete(this.recording);
2996
+ if (_refCount <= 0) return;
2997
+ _refCount--;
2998
+ if (_refCount === 0) {
2999
+ _restoreFetch();
3000
+ _restoreXhr();
3001
+ }
2560
3002
  }
2561
3003
  isExtendedHttpEnabled() {
2562
3004
  return localStorage.getItem("extendedHttpCommands") === "true";
@@ -2564,100 +3006,6 @@ var HttpMonitor = class {
2564
3006
  isFixtureModeEnabled() {
2565
3007
  return localStorage.getItem("fixtureMode") === "true";
2566
3008
  }
2567
- installFetch() {
2568
- if (this.originalFetch) return;
2569
- const originalFetch = window.fetch;
2570
- this.originalFetch = originalFetch;
2571
- window.fetch = async (input, init) => {
2572
- const response = await originalFetch(input, init);
2573
- try {
2574
- await this.handleFetchInterception(input, init, response.clone());
2575
- } catch {
2576
- }
2577
- return response;
2578
- };
2579
- }
2580
- uninstallFetch() {
2581
- if (!this.originalFetch) return;
2582
- window.fetch = this.originalFetch;
2583
- this.originalFetch = null;
2584
- }
2585
- installXhr() {
2586
- if (this.originalXHR) return;
2587
- this.originalXHR = window.XMLHttpRequest;
2588
- const OrigXHR = this.originalXHR;
2589
- const handleXhrInterception = this.handleXhrInterception.bind(this);
2590
- window.XMLHttpRequest = class extends OrigXHR {
2591
- _xhrMethod = "GET";
2592
- _xhrUrl = "";
2593
- _xhrRequestBody = null;
2594
- open(method, url, async = true, user, password) {
2595
- this._xhrMethod = method.toUpperCase();
2596
- this._xhrUrl = url instanceof URL ? url.href : url;
2597
- super.open(method, url, async, user, password);
2598
- }
2599
- send(body) {
2600
- if (typeof body === "string") {
2601
- this._xhrRequestBody = parseJsonObject(body);
2602
- }
2603
- this.addEventListener("load", () => {
2604
- try {
2605
- handleXhrInterception(
2606
- this._xhrMethod,
2607
- this._xhrUrl,
2608
- this._xhrRequestBody,
2609
- this.responseText
2610
- );
2611
- } catch {
2612
- }
2613
- });
2614
- super.send(body);
2615
- }
2616
- };
2617
- }
2618
- uninstallXhr() {
2619
- if (!this.originalXHR) return;
2620
- window.XMLHttpRequest = this.originalXHR;
2621
- this.originalXHR = null;
2622
- }
2623
- async handleFetchInterception(input, init, responseClone) {
2624
- const method = resolveMethod(input, init);
2625
- if (!INTERCEPTED_METHODS.includes(method)) return;
2626
- const url = resolveUrl(input);
2627
- const alias = generateAlias(method, url);
2628
- this.recording.registerInterceptor(method, url, alias);
2629
- const extendedHttp = this.isExtendedHttpEnabled();
2630
- const fixtureMode = this.isFixtureModeEnabled();
2631
- const requestBody = extendedHttp ? parseRequestBody(init) : null;
2632
- let responseText = null;
2633
- let responseBody = null;
2634
- if (extendedHttp || fixtureMode && method === "GET") {
2635
- try {
2636
- responseText = await responseClone.text();
2637
- if (extendedHttp) responseBody = parseJsonObject(responseText);
2638
- } catch {
2639
- }
2640
- }
2641
- if (fixtureMode && method === "GET" && responseText !== null) {
2642
- const pretty = prettyJsonOrNull(responseText);
2643
- if (pretty !== null) this.recording.registerFixture(`${alias}.json`, pretty);
2644
- }
2645
- const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
2646
- this.recording.addCommand(cmd);
2647
- }
2648
- handleXhrInterception(method, url, requestBody, responseText) {
2649
- if (!INTERCEPTED_METHODS.includes(method)) return;
2650
- const alias = generateAlias(method, url);
2651
- this.recording.registerInterceptor(method, url, alias);
2652
- if (this.isFixtureModeEnabled() && method === "GET") {
2653
- const pretty = prettyJsonOrNull(responseText);
2654
- if (pretty !== null) this.recording.registerFixture(`${alias}.json`, pretty);
2655
- }
2656
- const extendedHttp = this.isExtendedHttpEnabled();
2657
- const responseBody = extendedHttp ? parseJsonObject(responseText) : null;
2658
- const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
2659
- this.recording.addCommand(cmd);
2660
- }
2661
3009
  };
2662
3010
 
2663
3011
  // src/utils/styles.utils.ts
@@ -2889,23 +3237,24 @@ function makeSwalDraggable() {
2889
3237
  if (!dragArea) return;
2890
3238
  applyDraggable(swal, dragArea);
2891
3239
  }
2892
- function makeSwalDraggableByContentId(contentId) {
2893
- const content = document.getElementById(contentId);
2894
- if (!content) {
2895
- makeSwalDraggable();
2896
- return;
2897
- }
2898
- const swal = content.closest(".swal2-popup") ?? document.querySelector(".swal2-popup");
2899
- if (!swal) return;
2900
- const dragArea = resolveDragArea(swal);
2901
- if (!dragArea) return;
2902
- applyDraggable(swal, dragArea);
2903
- }
2904
3240
  function setSwal2DataCyAttribute(dataCy = "lib-e2e-cypress-for-dummys") {
2905
3241
  document.querySelector(".swal2-container")?.setAttribute("data-cy", dataCy);
2906
3242
  document.querySelector(".swal2-html-container")?.setAttribute("data-cy", dataCy);
2907
3243
  document.querySelector(".swal2-title")?.setAttribute("data-cy", dataCy);
2908
3244
  }
3245
+ function ensurePopupDimensions(popup, heightPx) {
3246
+ if (!popup) return;
3247
+ popup.style.height = `${heightPx}px`;
3248
+ const htmlContainer = popup.querySelector(".swal2-html-container");
3249
+ if (!htmlContainer) return;
3250
+ htmlContainer.style.flex = "1";
3251
+ htmlContainer.style.minHeight = "0";
3252
+ htmlContainer.style.overflow = "hidden";
3253
+ htmlContainer.style.padding = "0";
3254
+ htmlContainer.style.margin = "0";
3255
+ htmlContainer.style.display = "flex";
3256
+ htmlContainer.style.flexDirection = "column";
3257
+ }
2909
3258
 
2910
3259
  // src/utils/toast.utils.ts
2911
3260
  function showToast(message, isSuccess = true) {
@@ -2946,37 +3295,6 @@ function isLocalHost(hostname) {
2946
3295
  return h === "localhost" || h.endsWith(".localhost") || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0";
2947
3296
  }
2948
3297
 
2949
- // src/utils/widget-position.utils.ts
2950
- var TOGGLE_MARGIN = 30;
2951
- var DRAG_THRESHOLD = 5;
2952
- var DEFAULT_EDGE_OFFSET = 46;
2953
- function boxOffsetForDirection(dir) {
2954
- return {
2955
- x: dir.endsWith("left") ? 144 : 46,
2956
- y: dir.startsWith("up") ? 144 : 46
2957
- };
2958
- }
2959
- function clampTogglePosition(x, y, vw, vh, margin = TOGGLE_MARGIN) {
2960
- const maxX = Math.max(margin, vw - margin);
2961
- const maxY = Math.max(margin, vh - margin);
2962
- return {
2963
- x: Math.min(Math.max(x, margin), maxX),
2964
- y: Math.min(Math.max(y, margin), maxY)
2965
- };
2966
- }
2967
- function resolveExpandDirection(x, y, vw, vh) {
2968
- const vertical = y < vh / 2 ? "down" : "up";
2969
- const horizontal = x < vw / 2 ? "right" : "left";
2970
- return `${vertical}-${horizontal}`;
2971
- }
2972
- function defaultTogglePosition(vw, vh) {
2973
- return { x: vw - DEFAULT_EDGE_OFFSET, y: vh - DEFAULT_EDGE_OFFSET };
2974
- }
2975
- function boxTopLeftFor(toggleCentre, dir) {
2976
- const off = boxOffsetForDirection(dir);
2977
- return { x: toggleCentre.x - off.x, y: toggleCentre.y - off.y };
2978
- }
2979
-
2980
3298
  // src/components/test-previsualizer/test-previsualizer.styles.ts
2981
3299
  var TEST_PREVISUALIZER_STYLES = `
2982
3300
  :host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
@@ -3174,35 +3492,8 @@ var TestPrevisualizerElement = class extends HTMLElement {
3174
3492
  }
3175
3493
  }
3176
3494
  };
3177
- if (!customElements.get("test-previsualizer")) {
3178
- customElements.define("test-previsualizer", TestPrevisualizerElement);
3179
- }
3180
-
3181
- // src/models/issue-tracker.model.ts
3182
- var ISSUE_TRACKER_PRESETS = {
3183
- jira: { id: "jira", label: "Jira", urlPattern: "{baseUrl}/browse/{id}", idPattern: /^[A-Z][A-Z0-9]+-\d+$/ },
3184
- github: { id: "github", label: "GitHub Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
3185
- gitlab: { id: "gitlab", label: "GitLab Issues", urlPattern: "{baseUrl}/-/issues/{id}", idPattern: /^\d+$/ },
3186
- azure: { id: "azure", label: "Azure DevOps", urlPattern: "{baseUrl}/_workitems/edit/{id}", idPattern: /^\d+$/ },
3187
- bitbucket: { id: "bitbucket", label: "Bitbucket Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
3188
- custom: { id: "custom", label: "Custom", urlPattern: "{baseUrl}/{id}", idPattern: /.+/ }
3189
- };
3190
- var DEFAULT_ISSUE_TRACKER_CONFIG = {
3191
- enabled: false,
3192
- provider: "jira",
3193
- baseUrl: ""
3194
- };
3195
-
3196
- // src/utils/ticket.utils.ts
3197
- function buildTicketUrl(ticketId, config) {
3198
- if (!config.enabled || !ticketId.trim() || !config.baseUrl.trim()) return null;
3199
- const preset = ISSUE_TRACKER_PRESETS[config.provider];
3200
- const base = config.baseUrl.replace(/\/+$/, "");
3201
- return preset.urlPattern.replace("{baseUrl}", base).replace("{id}", encodeURIComponent(ticketId.trim()));
3202
- }
3203
- function isValidTicketId(ticketId, provider) {
3204
- if (!ticketId.trim()) return false;
3205
- return ISSUE_TRACKER_PRESETS[provider].idPattern.test(ticketId.trim());
3495
+ if (!customElements.get("lib-e2e-test-previsualizer")) {
3496
+ customElements.define("lib-e2e-test-previsualizer", TestPrevisualizerElement);
3206
3497
  }
3207
3498
 
3208
3499
  // src/components/save-test/save-test.styles.ts
@@ -3266,9 +3557,20 @@ var SAVE_TEST_STYLES = `
3266
3557
  transition: color 0.12s; padding: 0; background: none; border: none;
3267
3558
  }
3268
3559
  .chip-del:hover { color: #f85149; filter: none; }
3560
+ .discard-warn { color: #e6edf3; font-size: 13px; margin-bottom: 20px; line-height: 1.5; }
3269
3561
  `;
3270
3562
 
3271
3563
  // src/components/save-test/save-test.template.ts
3564
+ function renderSaveTestConfirmDiscard(t) {
3565
+ return `
3566
+ <div class="container">
3567
+ <p class="discard-warn">\u26A0 ${t("SAVE_TEST.CONFIRM_DISCARD")}</p>
3568
+ <div class="btn-row">
3569
+ <button class="btn-danger" id="btn-confirm-discard">${t("SAVE_TEST.CONFIRM_DISCARD_BTN")}</button>
3570
+ <button id="btn-back-discard">${t("SAVE_TEST.BACK_BTN")}</button>
3571
+ </div>
3572
+ </div>`;
3573
+ }
3272
3574
  function renderSaveTestAsk(t) {
3273
3575
  return `
3274
3576
  <div class="container">
@@ -3314,6 +3616,7 @@ function renderSaveTestDesc(description, notes, tags, ticketId, issueTrackerEnab
3314
3616
  var SaveTestElement = class extends HTMLElement {
3315
3617
  shadow;
3316
3618
  _step = "ask";
3619
+ _stepBeforeDiscard = "ask";
3317
3620
  description = "";
3318
3621
  notes = "";
3319
3622
  tags = [];
@@ -3341,8 +3644,17 @@ var SaveTestElement = class extends HTMLElement {
3341
3644
  this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() });
3342
3645
  }
3343
3646
  cancel() {
3647
+ this._stepBeforeDiscard = this._step === "confirm-discard" ? "ask" : this._step;
3648
+ this._step = "confirm-discard";
3649
+ this.render();
3650
+ }
3651
+ confirmDiscard() {
3344
3652
  this.dispatch("savetest", { description: null, notes: "", tags: [], ticketId: "" });
3345
3653
  }
3654
+ backFromDiscard() {
3655
+ this._step = this._stepBeforeDiscard;
3656
+ this.render();
3657
+ }
3346
3658
  restartComponent() {
3347
3659
  this._step = "ask";
3348
3660
  this.description = "";
@@ -3373,6 +3685,12 @@ var SaveTestElement = class extends HTMLElement {
3373
3685
  this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
3374
3686
  }
3375
3687
  render() {
3688
+ if (this._step === "confirm-discard") {
3689
+ this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestConfirmDiscard(this.t.bind(this))}`;
3690
+ this.shadow.getElementById("btn-confirm-discard")?.addEventListener("click", () => this.confirmDiscard());
3691
+ this.shadow.getElementById("btn-back-discard")?.addEventListener("click", () => this.backFromDiscard());
3692
+ return;
3693
+ }
3376
3694
  if (this._step === "ask") {
3377
3695
  this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestAsk(this.t.bind(this))}`;
3378
3696
  this.shadow.getElementById("btn-yes")?.addEventListener("click", () => this.askSave());
@@ -3414,8 +3732,8 @@ var SaveTestElement = class extends HTMLElement {
3414
3732
  }
3415
3733
  }
3416
3734
  };
3417
- if (!customElements.get("save-test")) {
3418
- customElements.define("save-test", SaveTestElement);
3735
+ if (!customElements.get("lib-e2e-save-test")) {
3736
+ customElements.define("lib-e2e-save-test", SaveTestElement);
3419
3737
  }
3420
3738
 
3421
3739
  // src/components/test-editor/test-editor.styles.ts
@@ -3644,7 +3962,9 @@ function renderTestRow(test, i, state, t) {
3644
3962
  const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
3645
3963
  const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
3646
3964
  const hasIcps = (test.interceptors ?? []).length > 0;
3647
- const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
3965
+ const ticketId = test.ticketId;
3966
+ const ticketComment = ticketId ? buildTicketComment(ticketId) + "\n" : "";
3967
+ const itBlockCode = `${ticketComment}it('${escapeSingleQuotes(test.name)}', () => {
3648
3968
  ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
3649
3969
  });`;
3650
3970
  const icpBlockCode = icps.length ? `beforeEach(() => {
@@ -3652,7 +3972,6 @@ ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
3652
3972
  ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
3653
3973
  });` : "";
3654
3974
  const notesHtml = expanded && test.notes ? `<p class="test-notes">${escHtml(test.notes)}</p>` : "";
3655
- const ticketId = test.ticketId;
3656
3975
  const ticketUrl = ticketId ? buildTicketUrl(ticketId, issueTrackerConfig) : null;
3657
3976
  const ticketHtml = ticketId ? ticketUrl ? `<a class="ticket-link" href="${escAttr(ticketUrl)}" target="_blank" rel="noopener noreferrer" title="${t("TEST_EDITOR.TICKET_LINK_TITLE")}">${escHtml(ticketId)}</a>` : `<span class="ticket-badge">${escHtml(ticketId)}</span>` : "";
3658
3977
  const body = expanded ? `
@@ -3901,8 +4220,90 @@ ${beforeEach}${itBlocks}
3901
4220
  });
3902
4221
  }
3903
4222
  };
3904
- if (!customElements.get("test-editor")) {
3905
- customElements.define("test-editor", TestEditorElement);
4223
+ if (!customElements.get("lib-e2e-test-editor")) {
4224
+ customElements.define("lib-e2e-test-editor", TestEditorElement);
4225
+ }
4226
+
4227
+ // src/models/active-session.model.ts
4228
+ var ACTIVE_SESSION_BREADCRUMB_KEY = "e2e-active-session";
4229
+ var RESUME_TTL_CONFIG_KEY = "resumeRecencyTtlMinutes";
4230
+ var DEFAULT_RESUME_TTL_MINUTES = 30;
4231
+
4232
+ // src/utils/login-setup.utils.ts
4233
+ var EXPORT_FN_RE = /^(?!\/\/).*\bexport\s+(?:async\s+)?function\s+(\w+)|^(?!\/\/).*\bexport\s+const\s+(\w+)\s*=\s*(?:async\s*)?\(/gm;
4234
+ function extractExportedFunctions(fileContent) {
4235
+ const names = [];
4236
+ let match;
4237
+ const re = new RegExp(EXPORT_FN_RE.source, EXPORT_FN_RE.flags);
4238
+ while ((match = re.exec(fileContent)) !== null) {
4239
+ const name = match[1] ?? match[2];
4240
+ if (name) names.push(name);
4241
+ }
4242
+ return names;
4243
+ }
4244
+ function buildLoginScaffold() {
4245
+ return [
4246
+ "/**",
4247
+ " * Login service scaffold \u2014 fill in your own auth logic.",
4248
+ " * This file is not committed to source control.",
4249
+ " */",
4250
+ "",
4251
+ "export function fetchAuthToken() {",
4252
+ " // TODO: implement \u2014 retrieve and return your auth token",
4253
+ "}",
4254
+ "",
4255
+ "export function setupRequestInterceptors() {",
4256
+ " // TODO: implement \u2014 configure cy.intercept() or set localStorage/cookies",
4257
+ "}",
4258
+ ""
4259
+ ].join("\n");
4260
+ }
4261
+ function buildLoginImportPath(fromFilePath, toServiceFilePath) {
4262
+ const fromParts = fromFilePath.replace(/\\/g, "/").split("/");
4263
+ const toParts = toServiceFilePath.replace(/\\/g, "/").split("/");
4264
+ fromParts.pop();
4265
+ let common = 0;
4266
+ while (common < fromParts.length && common < toParts.length - 1 && fromParts[common] === toParts[common]) {
4267
+ common++;
4268
+ }
4269
+ const ups = fromParts.length - common;
4270
+ const downs = toParts.slice(common);
4271
+ const serviceFile = downs[downs.length - 1].replace(/\.ts$/, "");
4272
+ const prefix = ups === 0 ? "." : Array(ups).fill("..").join("/");
4273
+ const middle = downs.slice(0, -1);
4274
+ return [prefix, ...middle, serviceFile].join("/");
4275
+ }
4276
+ function hasLoginBlocks(content, fnNames) {
4277
+ return fnNames.some((fn) => new RegExp(`\\b${fn}\\s*\\(`).test(content));
4278
+ }
4279
+ function injectLoginBlocksIntoExisting(content, importLine, beforeBlock, beforeEachBlock) {
4280
+ let result = content;
4281
+ if (importLine && !content.includes(importLine)) {
4282
+ result = `${importLine}
4283
+
4284
+ ${result}`;
4285
+ }
4286
+ const blocks = [beforeBlock, beforeEachBlock].filter(Boolean).join("");
4287
+ if (blocks) {
4288
+ const describeMatch = result.match(/(describe\s*\(.*?{)/s);
4289
+ if (describeMatch) {
4290
+ const insertPos = (describeMatch.index ?? 0) + describeMatch[0].length;
4291
+ result = result.slice(0, insertPos) + "\n" + blocks + result.slice(insertPos);
4292
+ }
4293
+ }
4294
+ return result;
4295
+ }
4296
+ function buildLoginBlocks(importPath, beforeFn, beforeEachFn) {
4297
+ if (!beforeFn && !beforeEachFn) {
4298
+ return { importLine: "", beforeBlock: "", beforeEachBlock: "" };
4299
+ }
4300
+ const fns = [...new Set([beforeFn, beforeEachFn].filter(Boolean))];
4301
+ const importLine = `import { ${fns.join(", ")} } from '${importPath}';`;
4302
+ const beforeBlock = beforeFn ? ` before(() => { ${beforeFn}(); });
4303
+ ` : "";
4304
+ const beforeEachBlock = beforeEachFn ? ` beforeEach(() => { ${beforeEachFn}(); });
4305
+ ` : "";
4306
+ return { importLine, beforeBlock, beforeEachBlock };
3906
4307
  }
3907
4308
 
3908
4309
  // src/components/configuration/configuration.styles.ts
@@ -4063,6 +4464,99 @@ var CONFIGURATION_STYLES = `
4063
4464
  .btn-export-confirm { background: #238636; border-color: #238636; color: #fff; }
4064
4465
  .btn-export-confirm:hover:not(:disabled) { background: #2ea043; border-color: #2ea043; }
4065
4466
  .btn-export-confirm:disabled { opacity: 0.45; cursor: not-allowed; }
4467
+
4468
+ /* \u2500\u2500 Form-element font reset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4469
+ input, select, button, textarea { font-family: inherit; }
4470
+
4471
+ /* \u2500\u2500 Primary action 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 */
4472
+ .btn-primary { background: #1f6feb; border-color: #1f6feb; color: #fff; }
4473
+ .btn-primary:hover { background: #388bfd; border-color: #388bfd; color: #fff; }
4474
+ .btn-primary:disabled { opacity: 0.45; cursor: not-allowed; }
4475
+
4476
+ /* \u2500\u2500 Login Setup overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4477
+ .ls-modal { max-width: 500px; }
4478
+ .ls-hd { display: flex; align-items: center; gap: 8px; }
4479
+ .ls-hd-icon { font-size: 15px; }
4480
+
4481
+ .ls-body {
4482
+ padding: 20px; overflow-y: auto; flex: 1;
4483
+ display: flex; flex-direction: column; gap: 20px;
4484
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
4485
+ }
4486
+
4487
+ .ls-section { display: flex; flex-direction: column; gap: 8px; }
4488
+ .ls-section-label {
4489
+ font-size: 10px; font-weight: 700; text-transform: uppercase;
4490
+ letter-spacing: 0.8px; color: #484f58;
4491
+ }
4492
+
4493
+ .ls-actions { display: flex; gap: 10px; }
4494
+ .ls-action-btn {
4495
+ flex: 1; padding: 14px 12px; border-radius: 8px;
4496
+ background: #0d1117; border: 1px solid #21262d; color: #8b949e;
4497
+ display: flex; flex-direction: column; align-items: center; gap: 6px;
4498
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
4499
+ }
4500
+ .ls-action-btn:hover { background: #161b22; color: #e6edf3; border-color: #388bfd; }
4501
+ .ls-action-icon { font-size: 22px; line-height: 1; }
4502
+ .ls-action-label { font-size: 12px; font-weight: 600; color: #c9d1d9; }
4503
+ .ls-action-btn:hover .ls-action-label { color: #e6edf3; }
4504
+ .ls-action-sub { font-size: 10px; color: #484f58; line-height: 1.4; text-align: center; }
4505
+
4506
+ .ls-file-row {
4507
+ display: flex; align-items: center; gap: 10px;
4508
+ padding: 10px 12px; background: #0d1117;
4509
+ border: 1px solid #21262d; border-radius: 8px;
4510
+ }
4511
+ .ls-file-icon { font-size: 14px; flex-shrink: 0; }
4512
+ .ls-file-name {
4513
+ flex: 1; font-size: 11px; color: #c9d1d9;
4514
+ font-family: 'Cascadia Code','Fira Code','Consolas',monospace;
4515
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
4516
+ min-width: 0;
4517
+ }
4518
+ .ls-btn-rescan {
4519
+ flex-shrink: 0; padding: 4px 10px; font-size: 11px;
4520
+ background: #161b22; border-color: #30363d; color: #8b949e;
4521
+ }
4522
+ .ls-btn-rescan:hover { background: #21262d; color: #e6edf3; }
4523
+
4524
+ .ls-input {
4525
+ width: 100%; padding: 8px 10px; background: #0d1117;
4526
+ border: 1px solid #30363d; border-radius: 6px;
4527
+ color: #c9d1d9; font-size: 12px; outline: none; font-family: inherit;
4528
+ }
4529
+ .ls-input:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.12); }
4530
+
4531
+ .ls-fns {
4532
+ display: flex; flex-wrap: wrap; gap: 6px;
4533
+ padding: 10px; background: #0d1117;
4534
+ border: 1px solid #21262d; border-radius: 6px; min-height: 38px;
4535
+ }
4536
+ .ls-fn-chip {
4537
+ padding: 3px 10px; font-size: 11px; color: #79c0ff;
4538
+ background: rgba(47,129,247,0.1); border: 1px solid rgba(47,129,247,0.2);
4539
+ border-radius: 12px; font-family: 'Cascadia Code','Fira Code','Consolas',monospace;
4540
+ }
4541
+
4542
+ .ls-field-block { display: flex; flex-direction: column; gap: 6px; }
4543
+ .ls-field-block + .ls-field-block { margin-top: 12px; }
4544
+ .ls-label { font-size: 11px; color: #8b949e; }
4545
+ .ls-select {
4546
+ width: 100%; padding: 8px 10px; background: #0d1117;
4547
+ border: 1px solid #30363d; border-radius: 6px;
4548
+ color: #c9d1d9; font-size: 12px; outline: none; cursor: pointer;
4549
+ }
4550
+ .ls-select:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.12); }
4551
+
4552
+ .ls-empty-state {
4553
+ display: flex; align-items: center; gap: 10px;
4554
+ padding: 14px; background: #0d1117; border: 1px solid #21262d;
4555
+ border-radius: 8px; font-size: 12px; color: #484f58;
4556
+ }
4557
+ .ls-empty-icon { font-size: 16px; }
4558
+
4559
+ .ls-ft { padding: 14px 20px; }
4066
4560
  `;
4067
4561
 
4068
4562
  // src/components/configuration/configuration.template.ts
@@ -4279,6 +4773,13 @@ function renderConfiguration(state, t) {
4279
4773
  </div>` : ""}
4280
4774
  </div>
4281
4775
 
4776
+ <!-- Login Setup -->
4777
+ <div class="card card-wide">
4778
+ <div class="card-hd">${t("CONFIG.LOGIN_SETUP_SECTION")}</div>
4779
+ ${state.loginSetupConfig ? `<p style="font-size:12px;color:#8b949e;margin:0 0 8px">${escHtml(state.loginSetupConfig.filePath)}</p>` : `<p style="font-size:12px;color:#8b949e;margin:0 0 8px">${t("CONFIG.LOGIN_SETUP_NOT_CONFIGURED")}</p>`}
4780
+ <button id="btn-open-login-setup" class="btn-sm">${t("CONFIG.LOGIN_SETUP_SET_UP_BTN")}</button>
4781
+ </div>
4782
+
4282
4783
  <!-- Data -->
4283
4784
  <div class="card card-wide">
4284
4785
  <div class="card-hd">${t("CONFIG.DATA_SECTION")}</div>
@@ -4292,7 +4793,93 @@ function renderConfiguration(state, t) {
4292
4793
  </div>
4293
4794
  </div>
4294
4795
 
4295
- </div>${renderExportOverlay(state, t)}`;
4796
+ </div>${renderExportOverlay(state, t)}${renderLoginSetupOverlay(state, t)}`;
4797
+ }
4798
+ function renderLoginSetupOverlay(state, t) {
4799
+ if (!state.isLoginSetupOpen) return "";
4800
+ const cfg = state.loginSetupConfig;
4801
+ const path = state.loginSetupDraftPath ?? cfg?.filePath ?? "";
4802
+ const fns = state.loginSetupDraftFunctions ?? cfg?.detectedFunctions ?? [];
4803
+ const beforeFn = state.loginSetupDraftBeforeFn !== void 0 ? state.loginSetupDraftBeforeFn : cfg?.beforeFn ?? null;
4804
+ const beforeEachFn = state.loginSetupDraftBeforeEachFn !== void 0 ? state.loginSetupDraftBeforeEachFn : cfg?.beforeEachFn ?? null;
4805
+ const hasContent = state.loginSetupDraftHasContent ?? !!cfg?.fileContent;
4806
+ const fnOption = (name, selected) => `<option value="${escAttr(name ?? "")}" ${selected === name ? "selected" : ""}>${name ? escHtml(name) : t("CONFIG.LOGIN_SETUP_NONE_OPTION")}</option>`;
4807
+ const fnDropdown = (id, label, selected) => `
4808
+ <div class="ls-field-block">
4809
+ <label class="ls-label" for="${id}">${escHtml(label)}</label>
4810
+ <select id="${id}" class="ls-select">
4811
+ ${fnOption(null, selected)}
4812
+ ${fns.map((fn) => fnOption(fn, selected)).join("")}
4813
+ </select>
4814
+ </div>`;
4815
+ const functionsSection = hasContent ? fns.length > 0 ? `<div class="ls-section">
4816
+ <span class="ls-section-label">${t("CONFIG.LOGIN_SETUP_FUNCTIONS_DETECTED")}</span>
4817
+ <div class="ls-fns">${fns.map((fn) => `<span class="ls-fn-chip">${escHtml(fn)}</span>`).join("")}</div>
4818
+ </div>
4819
+ <div class="ls-section">
4820
+ <span class="ls-section-label">${t("CONFIG.LOGIN_SETUP_BLOCK_ASSIGN")}</span>
4821
+ ${fnDropdown("login-setup-before-fn", t("CONFIG.LOGIN_SETUP_BEFORE_LABEL"), beforeFn)}
4822
+ ${fnDropdown("login-setup-before-each-fn", t("CONFIG.LOGIN_SETUP_BEFORE_EACH_LABEL"), beforeEachFn)}
4823
+ </div>` : `<div class="ls-empty-state">
4824
+ <span class="ls-empty-icon">\u{1F50D}</span>
4825
+ <span>${t("CONFIG.LOGIN_SETUP_NO_FUNCTIONS")}</span>
4826
+ </div>` : "";
4827
+ const fileSection = hasContent ? `<div class="ls-section">
4828
+ <span class="ls-section-label">${t("CONFIG.LOGIN_SETUP_FILE_NAME_LABEL")}</span>
4829
+ <div class="ls-file-row">
4830
+ <span class="ls-file-icon">\u{1F4C4}</span>
4831
+ <span class="ls-file-name">${escHtml(path)}</span>
4832
+ <button id="btn-login-setup-rescan" class="ls-btn-rescan">${t("CONFIG.LOGIN_SETUP_RESCAN")}</button>
4833
+ </div>
4834
+ </div>` : `<div class="ls-section">
4835
+ <span class="ls-section-label">${t("CONFIG.LOGIN_SETUP_FILE_NAME_LABEL")}</span>
4836
+ <input id="login-setup-filepath" type="text" class="ls-input"
4837
+ placeholder="${escAttr(t("CONFIG.LOGIN_SETUP_FILE_NAME_PH"))}"
4838
+ value="${escAttr(path)}" />
4839
+ </div>`;
4840
+ return `
4841
+ <div class="export-overlay" id="login-setup-overlay">
4842
+ <div class="export-modal ls-modal">
4843
+
4844
+ <div class="export-hd ls-hd">
4845
+ <span class="ls-hd-icon">\u{1F510}</span>
4846
+ ${t("CONFIG.LOGIN_SETUP_TITLE")}
4847
+ </div>
4848
+
4849
+ <div class="ls-body">
4850
+
4851
+ <div class="ls-section">
4852
+ <span class="ls-section-label">${t("CONFIG.LOGIN_SETUP_SOURCE_LABEL")}</span>
4853
+ <div class="ls-actions">
4854
+ <button id="btn-login-setup-create-scaffold" class="ls-action-btn">
4855
+ <span class="ls-action-icon">\u2728</span>
4856
+ <span class="ls-action-label">${t("CONFIG.LOGIN_SETUP_CREATE_SCAFFOLD")}</span>
4857
+ <span class="ls-action-sub">${t("CONFIG.LOGIN_SETUP_CREATE_SUB")}</span>
4858
+ </button>
4859
+ <button id="btn-login-setup-pick-file" class="ls-action-btn">
4860
+ <span class="ls-action-icon">\u{1F4C2}</span>
4861
+ <span class="ls-action-label">${t("CONFIG.LOGIN_SETUP_SELECT_EXISTING")}</span>
4862
+ <span class="ls-action-sub">${t("CONFIG.LOGIN_SETUP_SELECT_SUB")}</span>
4863
+ </button>
4864
+ </div>
4865
+ </div>
4866
+
4867
+ ${fileSection}
4868
+
4869
+ ${functionsSection}
4870
+
4871
+ </div>
4872
+
4873
+ <div class="export-ft ls-ft">
4874
+ <button id="btn-login-setup-clear" class="btn-danger">${t("CONFIG.LOGIN_SETUP_CLEAR_BTN")}</button>
4875
+ <span class="export-ft-actions">
4876
+ <button id="btn-login-setup-cancel">${t("CONFIG.LOGIN_SETUP_CANCEL_BTN")}</button>
4877
+ <button id="btn-login-setup-save" class="btn-primary">${t("CONFIG.LOGIN_SETUP_SAVE_BTN")}</button>
4878
+ </span>
4879
+ </div>
4880
+
4881
+ </div>
4882
+ </div>`;
4296
4883
  }
4297
4884
 
4298
4885
  // src/components/configuration/configuration.ts
@@ -4313,6 +4900,15 @@ var ConfigurationElement = class extends HTMLElement {
4313
4900
  exportSelectedIds = /* @__PURE__ */ new Set();
4314
4901
  exportSelectedTags = /* @__PURE__ */ new Set();
4315
4902
  issueTrackerConfig = { ...DEFAULT_ISSUE_TRACKER_CONFIG };
4903
+ isLoginSetupOpen = false;
4904
+ loginSetupConfig = null;
4905
+ loginSetupDraftPath = "";
4906
+ loginSetupDraftFunctions = [];
4907
+ loginSetupDraftBeforeFn = null;
4908
+ loginSetupDraftBeforeEachFn = null;
4909
+ loginSetupDraftHasContent = false;
4910
+ loginSetupDraftHandle = null;
4911
+ loginSetupDraftContent = "";
4316
4912
  filesystemGranted = false;
4317
4913
  cypressFolderName = null;
4318
4914
  constructor() {
@@ -4354,6 +4950,7 @@ var ConfigurationElement = class extends HTMLElement {
4354
4950
  provider: config?.["issueTrackerProvider"] ?? DEFAULT_ISSUE_TRACKER_CONFIG.provider,
4355
4951
  baseUrl: config?.["issueTrackerBaseUrl"] ?? ""
4356
4952
  };
4953
+ this.loginSetupConfig = await this.persistence.getLoginSetup();
4357
4954
  this.render();
4358
4955
  }
4359
4956
  async onLanguageChange(lang) {
@@ -4419,6 +5016,85 @@ var ConfigurationElement = class extends HTMLElement {
4419
5016
  await this.persistence.setConfig({ issueTrackerBaseUrl: baseUrl });
4420
5017
  this.dispatchEvent(new CustomEvent("issuetrackerchange", { detail: { ...this.issueTrackerConfig }, bubbles: true, composed: true }));
4421
5018
  }
5019
+ openLoginSetupPanel() {
5020
+ this.isLoginSetupOpen = true;
5021
+ const cfg = this.loginSetupConfig;
5022
+ this.loginSetupDraftHandle = null;
5023
+ this.loginSetupDraftContent = cfg?.fileContent ?? "";
5024
+ this.loginSetupDraftPath = cfg?.filePath ?? "";
5025
+ this.loginSetupDraftFunctions = cfg?.detectedFunctions ?? [];
5026
+ this.loginSetupDraftBeforeFn = cfg?.beforeFn ?? null;
5027
+ this.loginSetupDraftBeforeEachFn = cfg?.beforeEachFn ?? null;
5028
+ this.loginSetupDraftHasContent = !!cfg?.fileContent;
5029
+ this.render();
5030
+ }
5031
+ closeLoginSetupPanel() {
5032
+ this.isLoginSetupOpen = false;
5033
+ this.render();
5034
+ }
5035
+ async saveLoginSetupConfig(config) {
5036
+ await this.persistence.saveLoginSetup(config);
5037
+ this.loginSetupConfig = config;
5038
+ this.isLoginSetupOpen = false;
5039
+ this.render();
5040
+ }
5041
+ async clearLoginSetupConfig() {
5042
+ await this.persistence.clearLoginSetup();
5043
+ this.loginSetupConfig = null;
5044
+ this.render();
5045
+ }
5046
+ async loginSetupPickFile() {
5047
+ const currentPath = this.shadow.getElementById("login-setup-filepath")?.value.trim() ?? "";
5048
+ try {
5049
+ const [handle] = await window.showOpenFilePicker({
5050
+ types: [{ description: "TypeScript files", accept: { "text/plain": [".ts"] } }],
5051
+ multiple: false
5052
+ });
5053
+ this.loginSetupDraftHandle = handle;
5054
+ const file = await handle.getFile();
5055
+ this.loginSetupDraftContent = await file.text();
5056
+ this.loginSetupDraftHasContent = true;
5057
+ this.loginSetupDraftPath = currentPath || `cypress/common-services/${handle.name}`;
5058
+ this.loginSetupDraftFunctions = extractExportedFunctions(this.loginSetupDraftContent);
5059
+ this.render();
5060
+ } catch (e) {
5061
+ if (e?.name !== "AbortError") showToast(this.t("CONFIG.FOLDER_ERROR_TOAST"), false);
5062
+ }
5063
+ }
5064
+ async loginSetupCreateScaffold() {
5065
+ const currentPath = this.shadow.getElementById("login-setup-filepath")?.value.trim() ?? "";
5066
+ try {
5067
+ const handle = await window.showSaveFilePicker({
5068
+ suggestedName: "login.service.ts",
5069
+ types: [{ description: "TypeScript files", accept: { "text/plain": [".ts"] } }]
5070
+ });
5071
+ const content = buildLoginScaffold();
5072
+ const writable = await handle.createWritable();
5073
+ await writable.write(content);
5074
+ await writable.close();
5075
+ this.loginSetupDraftHandle = handle;
5076
+ this.loginSetupDraftContent = content;
5077
+ this.loginSetupDraftHasContent = true;
5078
+ this.loginSetupDraftPath = currentPath || `cypress/common-services/${handle.name}`;
5079
+ this.loginSetupDraftFunctions = extractExportedFunctions(content);
5080
+ this.render();
5081
+ } catch (e) {
5082
+ if (e?.name !== "AbortError") showToast(this.t("CONFIG.FOLDER_ERROR_TOAST"), false);
5083
+ }
5084
+ }
5085
+ async loginSetupRescan() {
5086
+ const currentPath = this.shadow.getElementById("login-setup-filepath")?.value.trim();
5087
+ if (currentPath !== void 0) this.loginSetupDraftPath = currentPath;
5088
+ try {
5089
+ if (this.loginSetupDraftHandle) {
5090
+ const file = await this.loginSetupDraftHandle.getFile();
5091
+ this.loginSetupDraftContent = await file.text();
5092
+ }
5093
+ this.loginSetupDraftFunctions = extractExportedFunctions(this.loginSetupDraftContent);
5094
+ this.render();
5095
+ } catch {
5096
+ }
5097
+ }
4422
5098
  async changeFolder() {
4423
5099
  try {
4424
5100
  await this.persistence.requestDirectoryPermissions();
@@ -4521,7 +5197,14 @@ var ConfigurationElement = class extends HTMLElement {
4521
5197
  exportTests: this.exportTests,
4522
5198
  exportSelectedIds: this.exportSelectedIds,
4523
5199
  exportSelectedTags: this.exportSelectedTags,
4524
- issueTrackerConfig: this.issueTrackerConfig
5200
+ issueTrackerConfig: this.issueTrackerConfig,
5201
+ isLoginSetupOpen: this.isLoginSetupOpen,
5202
+ loginSetupConfig: this.loginSetupConfig,
5203
+ loginSetupDraftPath: this.loginSetupDraftPath,
5204
+ loginSetupDraftFunctions: this.loginSetupDraftFunctions,
5205
+ loginSetupDraftBeforeFn: this.loginSetupDraftBeforeFn,
5206
+ loginSetupDraftBeforeEachFn: this.loginSetupDraftBeforeEachFn,
5207
+ loginSetupDraftHasContent: this.loginSetupDraftHasContent
4525
5208
  }, this.t.bind(this))}`;
4526
5209
  ;
4527
5210
  this.shadow.getElementById("lang-select").addEventListener(
@@ -4580,24 +5263,47 @@ var ConfigurationElement = class extends HTMLElement {
4580
5263
  this.shadow.querySelectorAll("[data-export-tag]").forEach(
4581
5264
  (el) => el.addEventListener("click", () => this.toggleExportTag(el.dataset["exportTag"] ?? ""))
4582
5265
  );
5266
+ this.shadow.getElementById("btn-open-login-setup")?.addEventListener("click", () => this.openLoginSetupPanel());
5267
+ this.shadow.getElementById("btn-login-setup-cancel")?.addEventListener("click", () => this.closeLoginSetupPanel());
5268
+ this.shadow.getElementById("btn-login-setup-pick-file")?.addEventListener("click", () => this.loginSetupPickFile());
5269
+ this.shadow.getElementById("btn-login-setup-create-scaffold")?.addEventListener("click", () => this.loginSetupCreateScaffold());
5270
+ this.shadow.getElementById("btn-login-setup-rescan")?.addEventListener("click", () => this.loginSetupRescan());
5271
+ this.shadow.getElementById("btn-login-setup-clear")?.addEventListener("click", () => this.clearLoginSetupConfig());
5272
+ this.shadow.getElementById("btn-login-setup-save")?.addEventListener("click", async () => {
5273
+ const pathEl = this.shadow.getElementById("login-setup-filepath");
5274
+ const beforeEl = this.shadow.getElementById("login-setup-before-fn");
5275
+ const beforeEachEl = this.shadow.getElementById("login-setup-before-each-fn");
5276
+ await this.saveLoginSetupConfig({
5277
+ enabled: true,
5278
+ filePath: pathEl?.value.trim() ?? this.loginSetupDraftPath,
5279
+ fileContent: this.loginSetupDraftContent,
5280
+ detectedFunctions: this.loginSetupDraftFunctions,
5281
+ beforeFn: beforeEl?.value || null,
5282
+ beforeEachFn: beforeEachEl?.value || null
5283
+ });
5284
+ });
5285
+ ;
4583
5286
  this.shadow.getElementById("file-input").addEventListener("change", async (e) => {
4584
5287
  const file = e.target.files?.[0];
4585
5288
  if (!file) return;
4586
5289
  try {
4587
5290
  await this.importAllData(file);
4588
- alert(this.t("CONFIG.IMPORT_SUCCESS"));
5291
+ showToast(this.t("CONFIG.IMPORT_SUCCESS"));
4589
5292
  } catch (err) {
4590
- alert(err.message ?? this.t("CONFIG.IMPORT_ERROR"));
5293
+ showToast(err.message ?? this.t("CONFIG.IMPORT_ERROR"), false);
4591
5294
  }
4592
5295
  const target = e.target;
4593
5296
  if (target) target.value = "";
4594
5297
  });
4595
5298
  }
4596
5299
  };
4597
- if (!customElements.get("e2e-configuration")) {
4598
- customElements.define("e2e-configuration", ConfigurationElement);
5300
+ if (!customElements.get("lib-e2e-configuration")) {
5301
+ customElements.define("lib-e2e-configuration", ConfigurationElement);
4599
5302
  }
4600
5303
 
5304
+ // src/components/advanced-test-editor/advanced-test-editor.ts
5305
+ import Swal from "sweetalert2";
5306
+
4601
5307
  // src/components/advanced-test-editor/advanced-test-editor.styles.ts
4602
5308
  var ADVANCED_TEST_EDITOR_STYLES = `
4603
5309
  :host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
@@ -4855,6 +5561,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
4855
5561
  isCreatingFolder = false;
4856
5562
  collapsedDirs = /* @__PURE__ */ new Set();
4857
5563
  sidebarWidth = 220;
5564
+ loginSetupConfig = null;
4858
5565
  hasPermission = false;
4859
5566
  needsReauth = false;
4860
5567
  _dirHandle = null;
@@ -4915,20 +5622,51 @@ var AdvancedTestEditorElement = class extends HTMLElement {
4915
5622
  const name = rawName.trim().replace(/\.cy\.ts$/, "");
4916
5623
  if (!name) return;
4917
5624
  const fileName = `${name}.cy.ts`;
4918
- const template = `describe('${escapeSingleQuotes(name)}', () => {
5625
+ const cfg = this.loginSetupConfig;
5626
+ if (cfg?.enabled && (cfg.beforeFn || cfg.beforeEachFn)) {
5627
+ const e2eName = this._e2eHandle.name ?? "e2e";
5628
+ const fromPath = `cypress/${e2eName}/${fileName}`;
5629
+ const importPath = buildLoginImportPath(fromPath, cfg.filePath);
5630
+ const { importLine, beforeBlock, beforeEachBlock } = buildLoginBlocks(
5631
+ importPath,
5632
+ cfg.beforeFn,
5633
+ cfg.beforeEachFn
5634
+ );
5635
+ const header = importLine ? `${importLine}
4919
5636
 
5637
+ ` : "";
5638
+ const inner = [beforeBlock, beforeEachBlock].filter(Boolean).join("");
5639
+ const template = `${header}describe('${escapeSingleQuotes(name)}', () => {
5640
+ ${inner}
4920
5641
  it('should ', () => {
4921
5642
 
4922
5643
  });
4923
5644
 
4924
5645
  });
4925
5646
  `;
4926
- try {
4927
- const fileHandle = await this._e2eHandle.getFileHandle(fileName, { create: true });
4928
- const writable = await fileHandle.createWritable();
4929
- await writable.write(template);
4930
- await writable.close();
4931
- } catch {
5647
+ try {
5648
+ const fileHandle = await this._e2eHandle.getFileHandle(fileName, { create: true });
5649
+ const writable = await fileHandle.createWritable();
5650
+ await writable.write(template);
5651
+ await writable.close();
5652
+ } catch {
5653
+ }
5654
+ } else {
5655
+ const template = `describe('${escapeSingleQuotes(name)}', () => {
5656
+
5657
+ it('should ', () => {
5658
+
5659
+ });
5660
+
5661
+ });
5662
+ `;
5663
+ try {
5664
+ const fileHandle = await this._e2eHandle.getFileHandle(fileName, { create: true });
5665
+ const writable = await fileHandle.createWritable();
5666
+ await writable.write(template);
5667
+ await writable.close();
5668
+ } catch {
5669
+ }
4932
5670
  }
4933
5671
  this.isCreatingFile = false;
4934
5672
  await this.getFoldersData();
@@ -4956,6 +5694,30 @@ var AdvancedTestEditorElement = class extends HTMLElement {
4956
5694
  const comment = this.testNotes ? this.transformationService.buildBlockComment(this.testNotes) + "\n" : "";
4957
5695
  content = this.transformationService.insertItBlock(content, comment + this.testItBlock);
4958
5696
  if (!content) return;
5697
+ const cfg = this.loginSetupConfig;
5698
+ if (cfg?.enabled && (cfg.beforeFn || cfg.beforeEachFn)) {
5699
+ const fns = [cfg.beforeFn, cfg.beforeEachFn].filter(Boolean);
5700
+ if (!hasLoginBlocks(content, fns)) {
5701
+ const e2eName = this._e2eHandle?.name ?? "e2e";
5702
+ const fileName = this.selectedFile?.name ?? "";
5703
+ const fromPath = `cypress/${e2eName}/${fileName}`;
5704
+ const importPath = buildLoginImportPath(fromPath, cfg.filePath);
5705
+ const { importLine, beforeBlock, beforeEachBlock } = buildLoginBlocks(importPath, cfg.beforeFn, cfg.beforeEachFn);
5706
+ const result = await Swal.fire({
5707
+ title: this.t("CONFIG.LOGIN_SETUP_INJECT_TITLE"),
5708
+ html: `<div style="padding:10px 18px 14px;color:#8b949e;font-size:13px;line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif">${this.t("CONFIG.LOGIN_SETUP_INJECT_TEXT")}</div>`,
5709
+ icon: "question",
5710
+ showCancelButton: true,
5711
+ confirmButtonText: this.t("CONFIG.LOGIN_SETUP_INJECT_CONFIRM"),
5712
+ cancelButtonText: this.t("CONFIG.LOGIN_SETUP_CANCEL_BTN"),
5713
+ confirmButtonColor: "#1f6feb",
5714
+ width: 460
5715
+ });
5716
+ if (result.isConfirmed) {
5717
+ content = injectLoginBlocksIntoExisting(content, importLine, beforeBlock, beforeEachBlock);
5718
+ }
5719
+ }
5720
+ }
4959
5721
  const writable = await this.selectedFileHandle.createWritable();
4960
5722
  await writable.write(content);
4961
5723
  await writable.close();
@@ -5150,8 +5912,8 @@ var AdvancedTestEditorElement = class extends HTMLElement {
5150
5912
  });
5151
5913
  }
5152
5914
  };
5153
- if (!customElements.get("advanced-test-editor")) {
5154
- customElements.define("advanced-test-editor", AdvancedTestEditorElement);
5915
+ if (!customElements.get("lib-e2e-advanced-test-editor")) {
5916
+ customElements.define("lib-e2e-advanced-test-editor", AdvancedTestEditorElement);
5155
5917
  }
5156
5918
 
5157
5919
  // src/components/file-preview/file-preview.styles.ts
@@ -5516,8 +6278,8 @@ var FilePreviewElement = class extends HTMLElement {
5516
6278
  });
5517
6279
  }
5518
6280
  };
5519
- if (!customElements.get("file-preview")) {
5520
- customElements.define("file-preview", FilePreviewElement);
6281
+ if (!customElements.get("lib-e2e-file-preview")) {
6282
+ customElements.define("lib-e2e-file-preview", FilePreviewElement);
5521
6283
  }
5522
6284
 
5523
6285
  // src/components/help-panel/help-panel.styles.ts
@@ -5612,12 +6374,12 @@ var REFERENCE_SECTIONS = [
5612
6374
  { title: "HELP.SEC_PANELS", items: ["HELP.PANEL_TESTS", "HELP.PANEL_COMMANDS", "HELP.PANEL_CONFIG", "HELP.PANEL_FILES"] },
5613
6375
  { title: "HELP.SEC_SELECTOR", items: ["HELP.SELECTOR_1", "HELP.SELECTOR_2"] },
5614
6376
  { title: "HELP.SEC_DATA", items: ["HELP.DATA_1", "HELP.DATA_2"] },
5615
- { title: "HELP.SEC_ADVANCED", items: ["HELP.ADV_INVISIBLE", "HELP.ADV_RUNNER", "HELP.ADV_CROSSAPP", "HELP.ADV_DRAG"] }
6377
+ { title: "HELP.SEC_ADVANCED", items: ["HELP.ADV_INVISIBLE", "HELP.ADV_RUNNER", "HELP.ADV_CROSSAPP", "HELP.ADV_DRAG", "HELP.ADV_LOGIN"] }
5616
6378
  ];
5617
6379
  var GUIDE_SECTIONS = [
5618
6380
  { title: "HELP.G_SEC_WORKFLOW", items: ["HELP.G_WF_1", "HELP.G_WF_2", "HELP.G_WF_3", "HELP.G_WF_4", "HELP.G_WF_5"] },
5619
- { title: "HELP.G_SEC_COVERED", items: ["HELP.G_COV_1", "HELP.G_COV_2", "HELP.G_COV_3", "HELP.G_COV_4", "HELP.G_COV_5", "HELP.G_COV_6"] },
5620
- { title: "HELP.G_SEC_NOTCOVERED", items: ["HELP.G_NC_1", "HELP.G_NC_2", "HELP.G_NC_3", "HELP.G_NC_4", "HELP.G_NC_5", "HELP.G_NC_6"] }
6381
+ { title: "HELP.G_SEC_COVERED", items: ["HELP.G_COV_1", "HELP.G_COV_2", "HELP.G_COV_3", "HELP.G_COV_4", "HELP.G_COV_5", "HELP.G_COV_6", "HELP.G_COV_7"] },
6382
+ { title: "HELP.G_SEC_NOTCOVERED", items: ["HELP.G_NC_1", "HELP.G_NC_2", "HELP.G_NC_3", "HELP.G_NC_4", "HELP.G_NC_5"] }
5621
6383
  ];
5622
6384
  function sectionsHtml(sections, t) {
5623
6385
  return sections.map(
@@ -5664,12 +6426,12 @@ var HelpPanelElement = class extends HTMLElement {
5664
6426
  });
5665
6427
  }
5666
6428
  };
5667
- if (!customElements.get("help-panel")) {
5668
- customElements.define("help-panel", HelpPanelElement);
6429
+ if (!customElements.get("lib-e2e-help-panel")) {
6430
+ customElements.define("lib-e2e-help-panel", HelpPanelElement);
5669
6431
  }
5670
6432
 
5671
6433
  // src/components/lib-e2e-recorder/lib-e2e-recorder.ts
5672
- import Swal from "sweetalert2";
6434
+ import Swal2 from "sweetalert2";
5673
6435
 
5674
6436
  // src/components/lib-e2e-recorder/lib-e2e-recorder.styles.ts
5675
6437
  var DIRECTIONS = ["up-left", "up-right", "down-left", "down-right"];
@@ -5870,6 +6632,139 @@ function renderRecorderWidget(rec, paused, t) {
5870
6632
  <div class="rec-badge">${paused ? t("RECORDER.BADGE_PAUSED") : t("RECORDER.BADGE_REC")}</div>`;
5871
6633
  }
5872
6634
 
6635
+ // src/components/lib-e2e-recorder/assertion-builder.ts
6636
+ var NO_VALUE_ASSERTIONS = /* @__PURE__ */ new Set(["be.visible", "not.exist", "be.disabled", "be.checked"]);
6637
+ function injectAssertionBuilder(container, t, onAddCommand) {
6638
+ container.insertAdjacentHTML("beforeend", `
6639
+ <div id="assertion-builder"
6640
+ style="padding:10px 12px;border-top:1px solid #21262d;background:#0d1117">
6641
+ <div style="font-size:10px;color:#484f58;text-transform:uppercase;letter-spacing:.8px;font-weight:600;margin-bottom:8px">
6642
+ ${t("RECORDER.ASSERTION_SECTION")}
6643
+ </div>
6644
+ <div style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end">
6645
+ <input id="assert-selector" type="text"
6646
+ placeholder="${t("RECORDER.ASSERT_SELECTOR_PH").replace(/"/g, "&quot;")}"
6647
+ style="flex:2;min-width:140px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
6648
+ border-radius:5px;color:#c9d1d9;font-size:11px;outline:none;
6649
+ font-family:'Cascadia Code','Fira Code',monospace"/>
6650
+ <select id="assert-type"
6651
+ style="padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;
6652
+ color:#c9d1d9;font-size:11px;outline:none;cursor:pointer;flex-shrink:0">
6653
+ <option value="be.visible">be.visible</option>
6654
+ <option value="not.exist">not.exist</option>
6655
+ <option value="be.disabled">be.disabled</option>
6656
+ <option value="be.checked">be.checked</option>
6657
+ <option value="contain.text">contain.text</option>
6658
+ <option value="have.value">have.value</option>
6659
+ <option value="have.length">have.length</option>
6660
+ <option value="have.class">have.class</option>
6661
+ <option value="have.attr">have.attr</option>
6662
+ </select>
6663
+ <input id="assert-value" type="text" placeholder="${t("RECORDER.ASSERT_VALUE_PH")}"
6664
+ style="flex:2;min-width:110px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
6665
+ border-radius:5px;color:#c9d1d9;font-size:11px;outline:none"/>
6666
+ <button id="btn-add-assertion"
6667
+ style="padding:5px 12px;border:none;border-radius:5px;cursor:pointer;
6668
+ font-size:11px;font-weight:500;background:#2f81f7;color:#fff;
6669
+ white-space:nowrap;flex-shrink:0">
6670
+ ${t("RECORDER.ASSERT_ADD_BTN")}
6671
+ </button>
6672
+ </div>
6673
+ </div>`);
6674
+ document.getElementById("btn-add-assertion")?.addEventListener("click", () => {
6675
+ const sel = document.getElementById("assert-selector").value.trim();
6676
+ const type = document.getElementById("assert-type").value;
6677
+ const val = document.getElementById("assert-value").value.trim();
6678
+ if (!sel) return;
6679
+ const s = escapeSingleQuotes(sel);
6680
+ const v = escapeSingleQuotes(val);
6681
+ const cmd = NO_VALUE_ASSERTIONS.has(type) || !val ? `cy.get('${s}').should('${type}')` : `cy.get('${s}').should('${type}', '${v}')`;
6682
+ onAddCommand(cmd);
6683
+ document.getElementById("assert-selector").value = "";
6684
+ document.getElementById("assert-value").value = "";
6685
+ });
6686
+ }
6687
+
6688
+ // src/components/lib-e2e-recorder/filesystem-setup.ts
6689
+ function mountFilesystemSetupContent(container, t, onSkip, onSelect) {
6690
+ container.innerHTML = `
6691
+ <div style="padding:16px 20px 20px;color:#8b949e;font-size:13px;line-height:1.7">
6692
+ <p>${t("RECORDER.FS_INTRO_HTML")}</p>
6693
+ <p style="margin-top:10px;margin-bottom:6px;font-size:11px;color:#8b949e">
6694
+ ${t("RECORDER.FS_STRUCTURE_HINT_HTML")}
6695
+ </p>
6696
+ <pre style="margin:0;padding:10px 14px;background:#0d1117;border:1px solid #21262d;
6697
+ border-radius:8px;font-size:11px;color:#c9d1d9;line-height:1.8;
6698
+ font-family:'Cascadia Code','Fira Code','Consolas',monospace">
6699
+ cypress/ <span style="color:#484f58">${t("RECORDER.FS_TREE_PICK_HINT")}</span>
6700
+ \u2514\u2500\u2500 e2e/ <span style="color:#484f58">${t("RECORDER.FS_TREE_READ_HINT")}</span>
6701
+ \u2514\u2500\u2500 *.cy.ts</pre>
6702
+ <p style="margin-top:8px;font-size:11px;color:#484f58">
6703
+ ${t("RECORDER.FS_PERMISSION_NOTE")}
6704
+ </p>
6705
+ <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:20px">
6706
+ <button id="fs-skip"
6707
+ style="padding:7px 16px;border:1px solid #30363d;border-radius:6px;cursor:pointer;
6708
+ font-size:12px;font-weight:500;background:transparent;color:#8b949e">
6709
+ ${t("RECORDER.FS_LATER_BTN")}
6710
+ </button>
6711
+ <button id="fs-select"
6712
+ style="padding:7px 16px;border:none;border-radius:6px;cursor:pointer;
6713
+ font-size:12px;font-weight:500;background:#2f81f7;color:#fff">
6714
+ ${t("RECORDER.FS_SELECT_BTN")}
6715
+ </button>
6716
+ </div>
6717
+ </div>`;
6718
+ document.getElementById("fs-skip")?.addEventListener("click", () => {
6719
+ void onSkip();
6720
+ });
6721
+ document.getElementById("fs-select")?.addEventListener("click", () => {
6722
+ void onSelect();
6723
+ });
6724
+ }
6725
+
6726
+ // src/utils/swal-mount.utils.ts
6727
+ function mountComponentInSwal(tagName, container, props, events) {
6728
+ const el = document.createElement(tagName);
6729
+ Object.assign(el, props);
6730
+ container.appendChild(el);
6731
+ for (const { name, handler } of events ?? []) {
6732
+ el.addEventListener(name, handler);
6733
+ }
6734
+ return el;
6735
+ }
6736
+
6737
+ // src/utils/widget-position.utils.ts
6738
+ var TOGGLE_MARGIN = 30;
6739
+ var DRAG_THRESHOLD = 5;
6740
+ var DEFAULT_EDGE_OFFSET = 46;
6741
+ function boxOffsetForDirection(dir) {
6742
+ return {
6743
+ x: dir.endsWith("left") ? 144 : 46,
6744
+ y: dir.startsWith("up") ? 144 : 46
6745
+ };
6746
+ }
6747
+ function clampTogglePosition(x, y, vw, vh, margin = TOGGLE_MARGIN) {
6748
+ const maxX = Math.max(margin, vw - margin);
6749
+ const maxY = Math.max(margin, vh - margin);
6750
+ return {
6751
+ x: Math.min(Math.max(x, margin), maxX),
6752
+ y: Math.min(Math.max(y, margin), maxY)
6753
+ };
6754
+ }
6755
+ function resolveExpandDirection(x, y, vw, vh) {
6756
+ const vertical = y < vh / 2 ? "down" : "up";
6757
+ const horizontal = x < vw / 2 ? "right" : "left";
6758
+ return `${vertical}-${horizontal}`;
6759
+ }
6760
+ function defaultTogglePosition(vw, vh) {
6761
+ return { x: vw - DEFAULT_EDGE_OFFSET, y: vh - DEFAULT_EDGE_OFFSET };
6762
+ }
6763
+ function boxTopLeftFor(toggleCentre, dir) {
6764
+ const off = boxOffsetForDirection(dir);
6765
+ return { x: toggleCentre.x - off.x, y: toggleCentre.y - off.y };
6766
+ }
6767
+
5873
6768
  // src/utils/fixture-convert.utils.ts
5874
6769
  function toFixtureInterceptors(interceptors, fixtures) {
5875
6770
  const fixtureNames = new Set(fixtures.map((f) => f.name));
@@ -5989,6 +6884,10 @@ var LibE2eRecorderElement = class extends HTMLElement {
5989
6884
  this.recording.destroy();
5990
6885
  this._needsRecordingRebuild = true;
5991
6886
  }
6887
+ // HttpMonitor reads extendedHttpCommands and fixtureMode synchronously inside
6888
+ // fetch/XHR interception handlers, so they must live in localStorage (sync).
6889
+ // IndexedDB is the persistent source of truth; this method mirrors it into
6890
+ // localStorage on every mount so the sync cache is always fresh.
5992
6891
  async initHttpConfig() {
5993
6892
  const config = await this.persistence.getConfig("extendedHttpCommands");
5994
6893
  if (config === null) {
@@ -6035,10 +6934,10 @@ var LibE2eRecorderElement = class extends HTMLElement {
6035
6934
  this.langUnsub = this.translation.onLangChange(() => this.render());
6036
6935
  }
6037
6936
  showSelectorPicker(target) {
6038
- import("./selector-picker-JM5HID3Z.js").then(() => {
6039
- const existing = document.querySelector("selector-picker");
6937
+ import("./selector-picker-KJUXPXHH.js").then(() => {
6938
+ const existing = document.querySelector("lib-e2e-selector-picker");
6040
6939
  if (existing) existing.remove();
6041
- const picker = document.createElement("selector-picker");
6940
+ const picker = document.createElement("lib-e2e-selector-picker");
6042
6941
  picker.targetElement = target;
6043
6942
  picker.recording = this.recording;
6044
6943
  picker.translation = this.translation;
@@ -6100,7 +6999,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
6100
6999
  }
6101
7000
  async promptResumeOrDiscard(session) {
6102
7001
  const t = this.translation.translate.bind(this.translation);
6103
- const result = await Swal.fire({
7002
+ const result = await Swal2.fire({
6104
7003
  title: t("RECORDER.SESSION_RESUME_TITLE"),
6105
7004
  html: `<div style="padding:8px 4px;color:#8b949e;font-size:13px;line-height:1.6"><p>${t("RECORDER.SESSION_RESUME_TEXT")}</p><p style="margin-top:6px;color:#c9d1d9"><b>${session.commands.length}</b> ${t("RECORDER.SESSION_RESUME_COUNT")}</p></div>`,
6106
7005
  showCancelButton: true,
@@ -6319,8 +7218,9 @@ var LibE2eRecorderElement = class extends HTMLElement {
6319
7218
  this.showFilesystemSetupDialog();
6320
7219
  }
6321
7220
  showFilesystemSetupDialog() {
6322
- Swal.fire({
6323
- title: this.translation.translate("RECORDER.FS_TITLE"),
7221
+ const t = this.translation.translate.bind(this.translation);
7222
+ Swal2.fire({
7223
+ title: t("RECORDER.FS_TITLE"),
6324
7224
  html: '<div id="fs-setup-content"></div>',
6325
7225
  showCloseButton: true,
6326
7226
  showConfirmButton: false,
@@ -6329,277 +7229,171 @@ var LibE2eRecorderElement = class extends HTMLElement {
6329
7229
  setSwal2DataCyAttribute();
6330
7230
  const container = document.getElementById("fs-setup-content");
6331
7231
  if (!container) return;
6332
- container.innerHTML = `
6333
- <div style="padding:16px 20px 20px;color:#8b949e;font-size:13px;line-height:1.7">
6334
- <p>${this.translation.translate("RECORDER.FS_INTRO_HTML")}</p>
6335
- <p style="margin-top:10px;margin-bottom:6px;font-size:11px;color:#8b949e">
6336
- ${this.translation.translate("RECORDER.FS_STRUCTURE_HINT_HTML")}
6337
- </p>
6338
- <pre style="margin:0;padding:10px 14px;background:#0d1117;border:1px solid #21262d;
6339
- border-radius:8px;font-size:11px;color:#c9d1d9;line-height:1.8;
6340
- font-family:'Cascadia Code','Fira Code','Consolas',monospace">
6341
- cypress/ <span style="color:#484f58">${this.translation.translate("RECORDER.FS_TREE_PICK_HINT")}</span>
6342
- \u2514\u2500\u2500 e2e/ <span style="color:#484f58">${this.translation.translate("RECORDER.FS_TREE_READ_HINT")}</span>
6343
- \u2514\u2500\u2500 *.cy.ts</pre>
6344
- <p style="margin-top:8px;font-size:11px;color:#484f58">
6345
- ${this.translation.translate("RECORDER.FS_PERMISSION_NOTE")}
6346
- </p>
6347
- <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:20px">
6348
- <button id="fs-skip"
6349
- style="padding:7px 16px;border:1px solid #30363d;border-radius:6px;cursor:pointer;
6350
- font-size:12px;font-weight:500;background:transparent;color:#8b949e">
6351
- ${this.translation.translate("RECORDER.FS_LATER_BTN")}
6352
- </button>
6353
- <button id="fs-select"
6354
- style="padding:7px 16px;border:none;border-radius:6px;cursor:pointer;
6355
- font-size:12px;font-weight:500;background:#2f81f7;color:#fff">
6356
- ${this.translation.translate("RECORDER.FS_SELECT_BTN")}
6357
- </button>
6358
- </div>
6359
- </div>`;
6360
- document.getElementById("fs-skip")?.addEventListener("click", async () => {
6361
- await this.persistence.setConfigKey("allowReadWriteFiles", "false");
6362
- Swal.close();
6363
- });
6364
- document.getElementById("fs-select")?.addEventListener("click", async () => {
6365
- try {
6366
- await this.persistence.requestDirectoryPermissions();
6367
- Swal.close();
6368
- showToast(this.translation.translate("RECORDER.FS_SUCCESS_TOAST"));
6369
- } catch (e) {
6370
- if (e?.name !== "AbortError") {
6371
- showToast(this.translation.translate("RECORDER.FS_ERROR_TOAST"), false);
7232
+ mountFilesystemSetupContent(
7233
+ container,
7234
+ t,
7235
+ async () => {
7236
+ await this.persistence.setConfigKey("allowReadWriteFiles", "false");
7237
+ Swal2.close();
7238
+ },
7239
+ async () => {
7240
+ try {
7241
+ await this.persistence.requestDirectoryPermissions();
7242
+ Swal2.close();
7243
+ showToast(t("RECORDER.FS_SUCCESS_TOAST"));
7244
+ } catch (e) {
7245
+ if (e?.name !== "AbortError") {
7246
+ showToast(t("RECORDER.FS_ERROR_TOAST"), false);
7247
+ }
6372
7248
  }
6373
7249
  }
6374
- });
7250
+ );
6375
7251
  }
6376
7252
  });
6377
7253
  }
6378
7254
  // ── dialogs ──────────────────────────────────────────────────────────────
6379
7255
  showCommandsDialog() {
6380
- this.toggleModal("isCommandsDialogOpen", () => {
6381
- Swal.fire({
7256
+ this.openSwalDialog(
7257
+ "isCommandsDialogOpen",
7258
+ "commands-modal-content",
7259
+ {
6382
7260
  title: this.translation.translate("MAIN_FRAME.DIALOG_COMMANDS"),
6383
7261
  html: '<div id="commands-modal-content" style="min-height:250px;padding:0"></div>',
6384
7262
  showCloseButton: true,
6385
- showConfirmButton: false,
6386
7263
  allowOutsideClick: false,
6387
7264
  backdrop: false,
6388
- width: 640,
6389
- color: "#e6edf3",
6390
- didOpen: () => {
6391
- makeSwalDraggable();
6392
- setSwal2DataCyAttribute();
7265
+ width: 640
7266
+ },
7267
+ (container) => {
7268
+ const child = mountComponentInSwal("lib-e2e-test-previsualizer", container, {
7269
+ translation: this.translation,
7270
+ commands: this.cypressCommands,
7271
+ interceptors: this.interceptors,
7272
+ editable: true
7273
+ }, [
7274
+ { name: "deletecommand", handler: (e) => this.recording.removeCommand(e.detail) },
7275
+ { name: "movecommand", handler: (e) => this.recording.moveCommand(e.detail.from, e.detail.to) },
7276
+ { name: "deleteinterceptor", handler: (e) => this.recording.removeInterceptor(e.detail) }
7277
+ ]);
7278
+ this._previsualizerRef = child;
7279
+ injectAssertionBuilder(container, this.translation.translate.bind(this.translation), (cmd) => this.recording.appendCommand(cmd));
7280
+ },
7281
+ {
7282
+ // Make the container click-through so the page underneath remains interactive
7283
+ onDidOpenPreamble: () => {
6393
7284
  const swalContainer = document.querySelector(".swal2-container");
6394
7285
  if (swalContainer) {
6395
7286
  swalContainer.style.pointerEvents = "none";
6396
7287
  const popup = swalContainer.querySelector(".swal2-popup");
6397
7288
  if (popup) popup.style.pointerEvents = "all";
6398
7289
  }
6399
- const container = document.getElementById("commands-modal-content");
6400
- if (!container) return;
6401
- const child = document.createElement("test-previsualizer");
6402
- child.translation = this.translation;
6403
- child.commands = this.cypressCommands;
6404
- child.interceptors = this.interceptors;
6405
- child.editable = true;
6406
- container.appendChild(child);
6407
- this._previsualizerRef = child;
6408
- child.addEventListener("deletecommand", (e) => {
6409
- this.recording.removeCommand(e.detail);
6410
- });
6411
- child.addEventListener("movecommand", (e) => {
6412
- this.recording.moveCommand(e.detail.from, e.detail.to);
6413
- });
6414
- child.addEventListener("deleteinterceptor", (e) => {
6415
- this.recording.removeInterceptor(e.detail);
6416
- });
6417
- const assertionHtml = `
6418
- <div id="assertion-builder"
6419
- style="padding:10px 12px;border-top:1px solid #21262d;background:#0d1117">
6420
- <div style="font-size:10px;color:#484f58;text-transform:uppercase;letter-spacing:.8px;font-weight:600;margin-bottom:8px">
6421
- ${this.translation.translate("RECORDER.ASSERTION_SECTION")}
6422
- </div>
6423
- <div style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end">
6424
- <input id="assert-selector" type="text"
6425
- placeholder="${this.translation.translate("RECORDER.ASSERT_SELECTOR_PH").replace(/"/g, "&quot;")}"
6426
- style="flex:2;min-width:140px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
6427
- border-radius:5px;color:#c9d1d9;font-size:11px;outline:none;
6428
- font-family:'Cascadia Code','Fira Code',monospace"/>
6429
- <select id="assert-type"
6430
- style="padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;
6431
- color:#c9d1d9;font-size:11px;outline:none;cursor:pointer;flex-shrink:0">
6432
- <option value="be.visible">be.visible</option>
6433
- <option value="not.exist">not.exist</option>
6434
- <option value="be.disabled">be.disabled</option>
6435
- <option value="be.checked">be.checked</option>
6436
- <option value="contain.text">contain.text</option>
6437
- <option value="have.value">have.value</option>
6438
- <option value="have.length">have.length</option>
6439
- <option value="have.class">have.class</option>
6440
- <option value="have.attr">have.attr</option>
6441
- </select>
6442
- <input id="assert-value" type="text" placeholder="${this.translation.translate("RECORDER.ASSERT_VALUE_PH")}"
6443
- style="flex:2;min-width:110px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
6444
- border-radius:5px;color:#c9d1d9;font-size:11px;outline:none"/>
6445
- <button id="btn-add-assertion"
6446
- style="padding:5px 12px;border:none;border-radius:5px;cursor:pointer;
6447
- font-size:11px;font-weight:500;background:#2f81f7;color:#fff;
6448
- white-space:nowrap;flex-shrink:0">
6449
- ${this.translation.translate("RECORDER.ASSERT_ADD_BTN")}
6450
- </button>
6451
- </div>
6452
- </div>`;
6453
- container.insertAdjacentHTML("beforeend", assertionHtml);
6454
- const NO_VALUE_ASSERTIONS = /* @__PURE__ */ new Set(["be.visible", "not.exist", "be.disabled", "be.checked"]);
6455
- document.getElementById("btn-add-assertion")?.addEventListener("click", () => {
6456
- const sel = document.getElementById("assert-selector").value.trim();
6457
- const type = document.getElementById("assert-type").value;
6458
- const val = document.getElementById("assert-value").value.trim();
6459
- if (!sel) return;
6460
- const s = escapeSingleQuotes(sel);
6461
- const v = escapeSingleQuotes(val);
6462
- const cmd = NO_VALUE_ASSERTIONS.has(type) || !val ? `cy.get('${s}').should('${type}')` : `cy.get('${s}').should('${type}', '${v}')`;
6463
- this.recording.appendCommand(cmd);
6464
- document.getElementById("assert-selector").value = "";
6465
- document.getElementById("assert-value").value = "";
6466
- });
6467
7290
  },
6468
- willClose: () => {
6469
- this.isCommandsDialogOpen = false;
7291
+ onWillClose: () => {
6470
7292
  this._previsualizerRef = null;
6471
7293
  }
6472
- });
6473
- this.resizePopup();
6474
- });
7294
+ }
7295
+ );
6475
7296
  }
6476
7297
  showSavedTestsDialog() {
6477
- this.toggleModal("isSavedTestsDialogOpen", () => {
6478
- Swal.fire({
7298
+ this.openSwalDialog(
7299
+ "isSavedTestsDialogOpen",
7300
+ "saved-tests-modal-content",
7301
+ {
6479
7302
  title: this.translation.translate("MAIN_FRAME.DIALOG_SAVED_TESTS"),
6480
7303
  html: '<div id="saved-tests-modal-content" style="min-height:250px;padding:0"></div>',
6481
7304
  showCloseButton: true,
6482
- showConfirmButton: false,
6483
- width: 680,
6484
- color: "#e6edf3",
6485
- didOpen: () => {
6486
- makeSwalDraggable();
6487
- setSwal2DataCyAttribute();
6488
- const container = document.getElementById("saved-tests-modal-content");
6489
- if (!container) return;
6490
- const child = document.createElement("test-editor");
6491
- child.persistence = this.persistence;
6492
- child.translation = this.translation;
6493
- child.issueTrackerConfig = this.issueTrackerConfig;
6494
- container.appendChild(child);
6495
- },
6496
- willClose: () => {
6497
- this.isSavedTestsDialogOpen = false;
6498
- }
6499
- });
6500
- this.resizePopup();
6501
- });
7305
+ width: 680
7306
+ },
7307
+ (container) => {
7308
+ mountComponentInSwal("lib-e2e-test-editor", container, {
7309
+ persistence: this.persistence,
7310
+ translation: this.translation,
7311
+ issueTrackerConfig: this.issueTrackerConfig
7312
+ });
7313
+ }
7314
+ );
6502
7315
  }
6503
7316
  showSaveTestDialog() {
6504
- this.toggleModal("isSaveTestDialogOpen", () => {
6505
- Swal.fire({
7317
+ this.openSwalDialog(
7318
+ "isSaveTestDialogOpen",
7319
+ "save-test-modal-content",
7320
+ {
6506
7321
  title: this.translation.translate("MAIN_FRAME.DIALOG_SAVE"),
6507
7322
  html: '<div id="save-test-modal-content" style="padding:0"></div>',
6508
- showCloseButton: true,
6509
- showConfirmButton: false,
7323
+ showCloseButton: false,
6510
7324
  allowOutsideClick: false,
6511
- color: "#e6edf3",
6512
- didOpen: () => {
6513
- makeSwalDraggable();
6514
- setSwal2DataCyAttribute();
6515
- const container = document.getElementById("save-test-modal-content");
6516
- if (!container) return;
6517
- const child = document.createElement("save-test");
6518
- child.translation = this.translation;
6519
- child.issueTrackerConfig = this.issueTrackerConfig;
6520
- container.appendChild(child);
6521
- child.addEventListener("savetest", (e) => {
7325
+ allowEscapeKey: false
7326
+ },
7327
+ (container) => {
7328
+ mountComponentInSwal("lib-e2e-save-test", container, {
7329
+ translation: this.translation,
7330
+ issueTrackerConfig: this.issueTrackerConfig
7331
+ }, [
7332
+ { name: "savetest", handler: (e) => {
6522
7333
  const { description, notes, tags, ticketId } = e.detail ?? {};
6523
7334
  this.onSaveTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? "");
6524
- Swal.close();
6525
- });
6526
- child.addEventListener("saveandexport", (e) => {
7335
+ Swal2.close();
7336
+ } },
7337
+ { name: "saveandexport", handler: (e) => {
6527
7338
  const { description, notes, tags, ticketId } = e.detail ?? {};
6528
7339
  this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? "");
6529
- Swal.close();
6530
- });
6531
- },
6532
- willClose: () => {
6533
- this.isSaveTestDialogOpen = false;
6534
- }
6535
- });
6536
- });
7340
+ Swal2.close();
7341
+ } }
7342
+ ]);
7343
+ },
7344
+ { skipResize: true }
7345
+ );
6537
7346
  }
6538
7347
  showSettingsDialog() {
6539
- this.toggleModal("isSettingsDialogOpen", () => {
6540
- Swal.fire({
7348
+ this.openSwalDialog(
7349
+ "isSettingsDialogOpen",
7350
+ "settings-modal-content",
7351
+ {
6541
7352
  title: this.translation.translate("MAIN_FRAME.SETTINGS"),
6542
7353
  html: '<div id="settings-modal-content" style="padding:0"></div>',
6543
7354
  showCloseButton: true,
6544
- showConfirmButton: false,
6545
- width: 520,
6546
- color: "#e6edf3",
6547
- didOpen: () => {
6548
- makeSwalDraggable();
6549
- setSwal2DataCyAttribute();
6550
- const container = document.getElementById("settings-modal-content");
6551
- if (!container) return;
6552
- const child = document.createElement("e2e-configuration");
6553
- child.persistence = this.persistence;
6554
- child.translation = this.translation;
6555
- container.appendChild(child);
6556
- child.addEventListener("smartselectorchange", (e) => {
7355
+ width: 520
7356
+ },
7357
+ (container) => {
7358
+ mountComponentInSwal("lib-e2e-configuration", container, {
7359
+ persistence: this.persistence,
7360
+ translation: this.translation
7361
+ }, [
7362
+ { name: "smartselectorchange", handler: (e) => {
6557
7363
  this.smartSelectorEnabled = e.detail;
6558
- });
6559
- child.addEventListener("starthiddenchange", (e) => {
7364
+ } },
7365
+ { name: "starthiddenchange", handler: (e) => {
6560
7366
  this.isVisible = !e.detail;
6561
7367
  this.style.display = this.isVisible ? "" : "none";
6562
- });
6563
- child.addEventListener("resetwidgetposition", () => this.resetWidgetPosition());
6564
- child.addEventListener("issuetrackerchange", (e) => {
7368
+ } },
7369
+ { name: "resetwidgetposition", handler: () => this.resetWidgetPosition() },
7370
+ { name: "issuetrackerchange", handler: (e) => {
6565
7371
  this.issueTrackerConfig = e.detail;
6566
- });
6567
- },
6568
- willClose: () => {
6569
- this.isSettingsDialogOpen = false;
6570
- }
6571
- });
6572
- this.resizePopup();
6573
- });
7372
+ } }
7373
+ ]);
7374
+ }
7375
+ );
6574
7376
  }
6575
7377
  showHelpDialog() {
6576
- this.toggleModal("isHelpDialogOpen", () => {
6577
- Swal.fire({
7378
+ this.openSwalDialog(
7379
+ "isHelpDialogOpen",
7380
+ "help-modal-content",
7381
+ {
6578
7382
  title: this.translation.translate("HELP.TITLE"),
6579
7383
  html: '<div id="help-modal-content" style="padding:0"></div>',
6580
7384
  showCloseButton: true,
6581
- showConfirmButton: false,
6582
- width: 560,
6583
- color: "#e6edf3",
6584
- didOpen: () => {
6585
- makeSwalDraggable();
6586
- setSwal2DataCyAttribute();
6587
- const container = document.getElementById("help-modal-content");
6588
- if (!container) return;
6589
- const child = document.createElement("help-panel");
6590
- child.translation = this.translation;
6591
- container.appendChild(child);
6592
- },
6593
- willClose: () => {
6594
- this.isHelpDialogOpen = false;
6595
- }
6596
- });
6597
- this.resizePopup();
6598
- });
7385
+ width: 560
7386
+ },
7387
+ (container) => {
7388
+ mountComponentInSwal("lib-e2e-help-panel", container, {
7389
+ translation: this.translation
7390
+ });
7391
+ }
7392
+ );
6599
7393
  }
6600
7394
  showAdvancedEditorDialog(testId) {
6601
7395
  this.toggleModal("isAdvancedEditorDialogOpen", () => {
6602
- Swal.fire({
7396
+ Swal2.fire({
6603
7397
  title: this.translation.translate("MAIN_FRAME.SHOW_ADVANCED_EDITOR"),
6604
7398
  html: '<div id="advanced-editor-modal-content" style="flex:1;min-height:0;display:flex;flex-direction:column;padding:0"></div>',
6605
7399
  showCloseButton: true,
@@ -6610,42 +7404,33 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6610
7404
  didOpen: () => {
6611
7405
  makeSwalDraggable();
6612
7406
  setSwal2DataCyAttribute();
6613
- const popup = Swal.getPopup();
6614
- if (popup) {
6615
- popup.style.height = "600px";
6616
- const htmlContainer = popup.querySelector(".swal2-html-container");
6617
- if (htmlContainer) {
6618
- htmlContainer.style.flex = "1";
6619
- htmlContainer.style.minHeight = "0";
6620
- htmlContainer.style.overflow = "hidden";
6621
- htmlContainer.style.padding = "0";
6622
- htmlContainer.style.margin = "0";
6623
- htmlContainer.style.display = "flex";
6624
- htmlContainer.style.flexDirection = "column";
6625
- }
6626
- }
7407
+ ensurePopupDimensions(Swal2.getPopup(), 600);
6627
7408
  const container = document.getElementById("advanced-editor-modal-content");
6628
7409
  if (!container) return;
6629
- const child = document.createElement("advanced-test-editor");
6630
- child.persistence = this.persistence;
6631
- child.translation = this.translation;
6632
- if (testId !== void 0) child.testId = testId;
6633
- container.appendChild(child);
6634
- child.addEventListener("selectorstrategychange", (e) => {
6635
- this.recording.selectorStrategy = e.detail;
6636
- });
6637
- child.addEventListener("closemodal", () => Swal.close());
6638
- child.addEventListener("openfileeditor", (e) => {
6639
- Swal.close();
6640
- setTimeout(() => this.showFileEditorDialog(
6641
- e.detail.handle,
6642
- e.detail.content,
6643
- e.detail.fileName,
6644
- e.detail.testId,
6645
- e.detail.itBlock,
6646
- e.detail.interceptorsBlock,
6647
- e.detail.notes
6648
- ), 150);
7410
+ const child = mountComponentInSwal("lib-e2e-advanced-test-editor", container, {
7411
+ persistence: this.persistence,
7412
+ translation: this.translation,
7413
+ ...testId !== void 0 ? { testId } : {}
7414
+ }, [
7415
+ { name: "selectorstrategychange", handler: (e) => {
7416
+ this.recording.selectorStrategy = e.detail;
7417
+ } },
7418
+ { name: "closemodal", handler: () => Swal2.close() },
7419
+ { name: "openfileeditor", handler: (e) => {
7420
+ Swal2.close();
7421
+ setTimeout(() => this.showFileEditorDialog(
7422
+ e.detail.handle,
7423
+ e.detail.content,
7424
+ e.detail.fileName,
7425
+ e.detail.testId,
7426
+ e.detail.itBlock,
7427
+ e.detail.interceptorsBlock,
7428
+ e.detail.notes
7429
+ ), 150);
7430
+ } }
7431
+ ]);
7432
+ void this.persistence.getLoginSetup().then((cfg) => {
7433
+ child.loginSetupConfig = cfg;
6649
7434
  });
6650
7435
  },
6651
7436
  willClose: () => {
@@ -6653,13 +7438,13 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6653
7438
  }
6654
7439
  });
6655
7440
  setTimeout(() => {
6656
- const popup = Swal.getPopup();
7441
+ const popup = Swal2.getPopup();
6657
7442
  if (popup) makeModalResizable(popup, { minWidth: 400, minHeight: 0 });
6658
7443
  }, 0);
6659
7444
  });
6660
7445
  }
6661
7446
  showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "", notes = "") {
6662
- Swal.fire({
7447
+ Swal2.fire({
6663
7448
  title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
6664
7449
  html: '<div id="file-editor-modal-content" style="flex:1;min-height:0;display:flex;flex-direction:column;padding:0"></div>',
6665
7450
  showCloseButton: false,
@@ -6670,47 +7455,35 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6670
7455
  didOpen: () => {
6671
7456
  makeSwalDraggable();
6672
7457
  setSwal2DataCyAttribute();
6673
- const popup = Swal.getPopup();
6674
- if (popup) {
6675
- popup.style.height = "640px";
6676
- const htmlContainer = popup.querySelector(".swal2-html-container");
6677
- if (htmlContainer) {
6678
- htmlContainer.style.flex = "1";
6679
- htmlContainer.style.minHeight = "0";
6680
- htmlContainer.style.overflow = "hidden";
6681
- htmlContainer.style.padding = "0";
6682
- htmlContainer.style.margin = "0";
6683
- htmlContainer.style.display = "flex";
6684
- htmlContainer.style.flexDirection = "column";
6685
- }
6686
- }
7458
+ ensurePopupDimensions(Swal2.getPopup(), 640);
6687
7459
  const container = document.getElementById("file-editor-modal-content");
6688
7460
  if (!container) return;
6689
- const child = document.createElement("file-preview");
6690
- child.translation = this.translation;
6691
- child.fileContent = content;
6692
- child.fileName = fileName;
6693
- child.closeLabel = this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR");
6694
- child.itBlock = itBlock;
6695
- child.interceptorsBlock = interceptorsBlock;
6696
- child.notes = notes;
6697
- container.appendChild(child);
6698
- child.addEventListener("close", () => {
6699
- Swal.close();
6700
- setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
6701
- });
6702
- child.addEventListener("save", async (e) => {
6703
- try {
6704
- const writable = await handle.createWritable();
6705
- await writable.write(e.detail);
6706
- await writable.close();
6707
- showToast(this.translation.translate("RECORDER.FILE_SAVED_TOAST"));
6708
- } catch {
6709
- showToast(this.translation.translate("RECORDER.FILE_SAVE_ERROR_TOAST"), false);
6710
- }
6711
- Swal.close();
6712
- setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
6713
- });
7461
+ mountComponentInSwal("lib-e2e-file-preview", container, {
7462
+ translation: this.translation,
7463
+ fileContent: content,
7464
+ fileName,
7465
+ closeLabel: this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR"),
7466
+ itBlock,
7467
+ interceptorsBlock,
7468
+ notes
7469
+ }, [
7470
+ { name: "close", handler: () => {
7471
+ Swal2.close();
7472
+ setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
7473
+ } },
7474
+ { name: "save", handler: async (e) => {
7475
+ try {
7476
+ const writable = await handle.createWritable();
7477
+ await writable.write(e.detail);
7478
+ await writable.close();
7479
+ showToast(this.translation.translate("RECORDER.FILE_SAVED_TOAST"));
7480
+ } catch {
7481
+ showToast(this.translation.translate("RECORDER.FILE_SAVE_ERROR_TOAST"), false);
7482
+ }
7483
+ Swal2.close();
7484
+ setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
7485
+ } }
7486
+ ]);
6714
7487
  }
6715
7488
  });
6716
7489
  this.resizePopup();
@@ -6719,6 +7492,14 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6719
7492
  async onSaveTest(description, tags = [], notes = "", ticketId = "") {
6720
7493
  if (!description) return;
6721
7494
  await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes, ticketId || void 0);
7495
+ const uploadedFiles = this.recording.getUploadedFilesSnapshot();
7496
+ if (uploadedFiles.length > 0) {
7497
+ const filenames = uploadedFiles.map((f) => f.filename).join(", ");
7498
+ showToast(
7499
+ this.translation.translate("RECORDER.FILE_UPLOAD_MANUAL_TOAST").replace("{files}", filenames),
7500
+ false
7501
+ );
7502
+ }
6722
7503
  this.recording.clearCommands();
6723
7504
  this.clearRecordingHistory();
6724
7505
  this.cypressCommands = [];
@@ -6740,6 +7521,29 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6740
7521
  showToast(this.translation.translate("RECORDER.FIXTURES_NO_FOLDER_TOAST"), false);
6741
7522
  }
6742
7523
  }
7524
+ const uploadedFiles = this.recording.getUploadedFilesSnapshot();
7525
+ if (uploadedFiles.length > 0) {
7526
+ const filenames = uploadedFiles.map((f) => f.filename).join(", ");
7527
+ const hasAccess = await this.persistence.hasDirectoryAccess();
7528
+ if (hasAccess) {
7529
+ try {
7530
+ for (const uf of uploadedFiles) {
7531
+ await this.persistence.writeUploadedFile(uf.filename, uf.bytes);
7532
+ }
7533
+ showToast(this.translation.translate("RECORDER.FILE_UPLOAD_COPIED_TOAST").replace("{files}", filenames));
7534
+ } catch {
7535
+ showToast(
7536
+ this.translation.translate("RECORDER.FILE_UPLOAD_COPY_ERROR_TOAST").replace("{files}", filenames),
7537
+ false
7538
+ );
7539
+ }
7540
+ } else {
7541
+ showToast(
7542
+ this.translation.translate("RECORDER.FILE_UPLOAD_MANUAL_TOAST").replace("{files}", filenames),
7543
+ false
7544
+ );
7545
+ }
7546
+ }
6743
7547
  const id = await this.persistence.insertTest(description, finalCommands, finalInterceptors, tags, notes, ticketId || void 0);
6744
7548
  this.recording.clearCommands();
6745
7549
  this.clearRecordingHistory();
@@ -6750,7 +7554,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6750
7554
  // ── helpers ───────────────────────────────────────────────────────────────
6751
7555
  toggleModal(flag, openFn) {
6752
7556
  if (this[flag]) {
6753
- Swal.close();
7557
+ Swal2.close();
6754
7558
  this[flag] = false;
6755
7559
  return;
6756
7560
  }
@@ -6759,10 +7563,32 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6759
7563
  }
6760
7564
  resizePopup() {
6761
7565
  setTimeout(() => {
6762
- const popup = Swal.getPopup();
7566
+ const popup = Swal2.getPopup();
6763
7567
  if (popup) makeModalResizable(popup, { minWidth: 400, minHeight: 200 });
6764
7568
  }, 0);
6765
7569
  }
7570
+ openSwalDialog(flag, contentId, swalOptions, onMount, extra) {
7571
+ this.toggleModal(flag, () => {
7572
+ Swal2.fire({
7573
+ showConfirmButton: false,
7574
+ color: "#e6edf3",
7575
+ ...swalOptions,
7576
+ didOpen: () => {
7577
+ makeSwalDraggable();
7578
+ setSwal2DataCyAttribute();
7579
+ extra?.onDidOpenPreamble?.();
7580
+ const container = document.getElementById(contentId);
7581
+ if (!container) return;
7582
+ onMount(container);
7583
+ },
7584
+ willClose: () => {
7585
+ this[flag] = false;
7586
+ extra?.onWillClose?.();
7587
+ }
7588
+ });
7589
+ if (!extra?.skipResize) this.resizePopup();
7590
+ });
7591
+ }
6766
7592
  render() {
6767
7593
  const rec = this.isRecording;
6768
7594
  const paused = this.isPaused;
@@ -6791,16 +7617,13 @@ if (!customElements.get("lib-e2e-recorder")) {
6791
7617
  }
6792
7618
 
6793
7619
  // src/index.ts
6794
- var VERSION = "0.9.0";
7620
+ var VERSION = "1.0.0";
6795
7621
  export {
6796
- ACTIVE_SESSION_BREADCRUMB_KEY,
6797
7622
  AdvancedTestEditorElement,
6798
7623
  AdvancedTestTransformationService,
6799
7624
  ConfigurationElement,
6800
7625
  DB_SCHEMA,
6801
7626
  DB_STORE_NAMES,
6802
- DEFAULT_RESUME_TTL_MINUTES,
6803
- DRAG_THRESHOLD,
6804
7627
  FilePreviewElement,
6805
7628
  HelpPanelElement,
6806
7629
  HttpMonitor,
@@ -6809,24 +7632,16 @@ export {
6809
7632
  LOCALE_BY_LANG,
6810
7633
  LibE2eRecorderElement,
6811
7634
  PersistenceService,
6812
- RESUME_TTL_CONFIG_KEY,
6813
7635
  RecordingService,
6814
7636
  SCROLLBAR_STYLES,
6815
7637
  SUPPORTED_LANGS,
6816
7638
  SaveTestElement,
6817
7639
  SelectorPickerElement,
6818
- Subject,
6819
- TOGGLE_MARGIN,
6820
7640
  TestEditorElement,
6821
7641
  TestPrevisualizerElement,
6822
7642
  TransformationService,
6823
7643
  TranslationService,
6824
7644
  VERSION,
6825
- advancedTestTransformationService,
6826
- boxOffsetForDirection,
6827
- boxTopLeftFor,
6828
- clampTogglePosition,
6829
- defaultTogglePosition,
6830
7645
  generateAlias,
6831
7646
  inferAssertionCommand,
6832
7647
  injectStyles,
@@ -6835,13 +7650,7 @@ export {
6835
7650
  localeForLang,
6836
7651
  makeModalResizable,
6837
7652
  makeSwalDraggable,
6838
- makeSwalDraggableByContentId,
6839
- persistenceService,
6840
- resolveExpandDirection,
6841
7653
  selectTestsForExport,
6842
- setSwal2DataCyAttribute,
6843
- showToast,
6844
- transformationService,
6845
- translationService
7654
+ showToast
6846
7655
  };
6847
7656
  //# sourceMappingURL=index.js.map