lib-e2e-cypress-for-dummys-ts 0.6.1 → 0.9.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.cjs CHANGED
@@ -469,6 +469,7 @@ __export(index_exports, {
469
469
  DEFAULT_RESUME_TTL_MINUTES: () => DEFAULT_RESUME_TTL_MINUTES,
470
470
  DRAG_THRESHOLD: () => DRAG_THRESHOLD,
471
471
  FilePreviewElement: () => FilePreviewElement,
472
+ HelpPanelElement: () => HelpPanelElement,
472
473
  HttpMonitor: () => HttpMonitor,
473
474
  INPUT_TYPES: () => INPUT_TYPES,
474
475
  LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES: () => LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
@@ -494,6 +495,7 @@ __export(index_exports, {
494
495
  clampTogglePosition: () => clampTogglePosition,
495
496
  defaultTogglePosition: () => defaultTogglePosition,
496
497
  generateAlias: () => generateAlias,
498
+ inferAssertionCommand: () => inferAssertionCommand,
497
499
  injectStyles: () => injectStyles,
498
500
  isLang: () => isLang,
499
501
  isLocalHost: () => isLocalHost,
@@ -645,7 +647,10 @@ var I18N_ES = {
645
647
  SAVE_AND_EDIT: "\u{1F4DD} Guardar y editar",
646
648
  REMOVE_TAG_TITLE: "Quitar",
647
649
  NOTES_LABEL: "Descripci\xF3n / Notas (opcional):",
648
- NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc."
650
+ NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc.",
651
+ TICKET_LABEL: "Ticket / Issue (opcional):",
652
+ TICKET_PLACEHOLDER: "Ej: PROJ-123, #42",
653
+ TICKET_WARN: "ID no v\xE1lido para el proveedor configurado (se guardar\xE1 igualmente)"
649
654
  },
650
655
  TEST_EDITOR: {
651
656
  NO_TAGS: "Sin etiquetas",
@@ -659,7 +664,11 @@ var I18N_ES = {
659
664
  COPY_ICPS_BTN: "\u{1F4CB} Copiar interceptores",
660
665
  DEFAULT_DESCRIBE: "Suite de tests",
661
666
  NO_TESTS: "No hay tests guardados",
662
- DELETE_TITLE: "Eliminar"
667
+ DELETE_TITLE: "Eliminar",
668
+ GROUP_BY_TICKET: "\u{1F3AB} Agrupar por ticket",
669
+ UNGROUP_TICKET: "\u{1F3AB} Desagrupar",
670
+ NO_TICKET: "Sin ticket",
671
+ TICKET_LINK_TITLE: "Abrir ticket"
663
672
  },
664
673
  CONFIG: {
665
674
  LANG_SECTION: "\u{1F310} Idioma",
@@ -667,6 +676,9 @@ var I18N_ES = {
667
676
  HTTP_SECTION: "\u26A1 HTTP Avanzado",
668
677
  HTTP_TITLE: "Validaciones de body",
669
678
  HTTP_SUB: "GET \u2192 response \xB7 POST/PUT \u2192 request",
679
+ FIXTURE_SECTION: "\u{1F9EA} Fixtures HTTP",
680
+ FIXTURE_TITLE: "Grabar respuestas como fixtures",
681
+ FIXTURE_SUB: "Las GET se guardan en cypress/fixtures y se stubean con cy.intercept({ fixture }).",
670
682
  SELECTOR_SECTION: "\u{1F3AF} Estrategia de selector",
671
683
  SELECTOR_LABEL: "Atributo preferido para cy.get()",
672
684
  SELECTOR_OPT_DATACY: "data-cy (por defecto)",
@@ -714,7 +726,13 @@ var I18N_ES = {
714
726
  WIDGET_POSITION_SECTION: "\u{1F9F2} Posici\xF3n del widget",
715
727
  WIDGET_POSITION_HINT: "Arrastra el bot\xF3n de grabar para moverlo donde no estorbe. Se recuerda su posici\xF3n.",
716
728
  WIDGET_POSITION_RESET_BTN: "\u21BA Restablecer posici\xF3n",
717
- WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida"
729
+ WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida",
730
+ ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue tracker",
731
+ ISSUE_TRACKER_TITLE: "Vincular tests a tickets",
732
+ ISSUE_TRACKER_SUB: "A\xF1ade un ID de ticket al guardar y genera un enlace clickable en el listado.",
733
+ ISSUE_TRACKER_PROVIDER: "Proveedor",
734
+ ISSUE_TRACKER_BASE_URL: "URL base",
735
+ ISSUE_TRACKER_BASE_URL_PH: "Ej: https://miproyecto.atlassian.net"
718
736
  },
719
737
  SELECTOR_PICKER: {
720
738
  TITLE: "Selecciona un elemento del DOM",
@@ -803,6 +821,8 @@ var I18N_ES = {
803
821
  FILE_EDITOR_TITLE: "\u270F\uFE0F Editor de archivo",
804
822
  FILE_SAVED_TOAST: "\u2713 Archivo guardado",
805
823
  FILE_SAVE_ERROR_TOAST: "Error al guardar el archivo",
824
+ FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures escritos en cypress/fixtures",
825
+ FIXTURES_NO_FOLDER_TOAST: "No se pudieron escribir los fixtures (configura la carpeta Cypress)",
806
826
  BTN_CONFIG: "Config",
807
827
  BTN_FILES: "Ficheros",
808
828
  BTN_COMMANDS: "Comandos",
@@ -817,7 +837,76 @@ var I18N_ES = {
817
837
  SESSION_RESUME_TEXT: "Hay una grabaci\xF3n activa sin terminar.",
818
838
  SESSION_RESUME_COUNT: "comandos capturados",
819
839
  SESSION_CONTINUE_BTN: "Continuar grabando",
820
- SESSION_DISCARD_BTN: "Descartar"
840
+ SESSION_DISCARD_BTN: "Descartar",
841
+ BTN_HELP: "Ayuda"
842
+ },
843
+ HELP: {
844
+ TITLE: "\u2753 Ayuda \u2014 Gu\xEDa r\xE1pida",
845
+ INTRO: "Todo lo que puede hacer el grabador.",
846
+ SEC_RECORDING: "\u23FA Grabar",
847
+ RECORDING_1: "Pulsa \u23FA (o Ctrl+R) para empezar; \u23F9 para parar y guardar.",
848
+ RECORDING_2: "Pausa / reanuda con \u23F8 (o Ctrl+P).",
849
+ SEC_SHORTCUTS: "\u2328\uFE0F Atajos de teclado",
850
+ SC_RECORD: "Ctrl+R \u2014 Grabar / detener",
851
+ SC_PAUSE: "Ctrl+P \u2014 Pausar / reanudar",
852
+ SC_TESTS: "Ctrl+1 \u2014 Panel de tests guardados",
853
+ SC_COMMANDS: "Ctrl+2 \u2014 Panel de comandos",
854
+ SC_CONFIG: "Ctrl+3 \u2014 Configuraci\xF3n",
855
+ SC_VISIBILITY: "Ctrl+Shift+E \u2014 Mostrar / ocultar el widget",
856
+ SC_HELP: "Ctrl+Shift+H \u2014 Esta ayuda",
857
+ SEC_CAPTURED: "\u{1F3AF} Qu\xE9 se graba",
858
+ CAP_CLICK: "Click \u2192 .click()",
859
+ CAP_DBLCLICK: "Doble-click \u2192 .dblclick()",
860
+ CAP_RIGHTCLICK: "Click derecho \u2192 .rightclick()",
861
+ CAP_TYPE: "Escribir en un campo \u2192 .clear().type(...)",
862
+ CAP_CHECK: "Checkbox / radio \u2192 .check() / .uncheck()",
863
+ CAP_SELECT: "<select> \u2192 .select(...)",
864
+ CAP_KEYS: 'Enter / Escape \u2192 .type("{enter}") / ("{esc}")',
865
+ CAP_ROUTE: "Cambio de ruta (SPA) \u2192 cy.url().should(...)",
866
+ CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
867
+ SEC_ASSERT: "\u2705 Aserciones",
868
+ ASSERT_1: "Alt+click sobre un elemento para verificarlo sin ejecutar su acci\xF3n (texto, valor, checked o visible).",
869
+ ASSERT_2: "O usa el constructor de aserciones en el panel de Comandos.",
870
+ SEC_PANELS: "\u{1F5C2} Paneles",
871
+ PANEL_TESTS: "\u{1F4CB} Tests \u2014 guardados: filtrar por tag, copiar, combinar, exportar.",
872
+ PANEL_COMMANDS: "\u2328\uFE0F Comandos \u2014 de la grabaci\xF3n actual: reordenar, borrar, a\xF1adir aserciones.",
873
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 idioma, selector, HTTP, carpeta Cypress, datos, visibilidad, posici\xF3n.",
874
+ PANEL_FILES: "\u{1F4C1} Ficheros \u2014 inserta tests en tus .cy.ts y lanza el runner.",
875
+ SEC_SELECTOR: "\u{1F50E} Selectores",
876
+ SELECTOR_1: "Elige el atributo preferido (data-cy, data-testid, aria-label, id) en Config.",
877
+ SELECTOR_2: "Si un click no tiene selector fiable, aparece un picker para elegir el mejor ancestro.",
878
+ SEC_DATA: "\u{1F4BE} Datos",
879
+ DATA_1: "Exporta tests a JSON (todo / selecci\xF3n / por tags).",
880
+ DATA_2: "Importa un JSON: se fusiona con lo existente (no se borra nada).",
881
+ SEC_ADVANCED: "\u{1F680} Avanzado",
882
+ ADV_INVISIBLE: "Modo invisible: start-hidden + Ctrl+Shift+E para grabar en producci\xF3n sin molestar.",
883
+ ADV_RUNNER: "Runner local: npx lib-e2e-cypress-runner para lanzar un spec headless desde el editor.",
884
+ ADV_CROSSAPP: "Cross-app: la grabaci\xF3n sobrevive al cambiar de micro-frontend (mismo origen).",
885
+ ADV_DRAG: "Arrastra el bot\xF3n \u23FA para mover el widget; recuerda su posici\xF3n.",
886
+ CLOSE: "Cerrar",
887
+ TAB_REFERENCE: "Referencia r\xE1pida",
888
+ TAB_GUIDE: "Gu\xEDa de uso",
889
+ GUIDE_INTRO: "C\xF3mo sacarle partido al grabador, qu\xE9 cubre y qu\xE9 no.",
890
+ G_SEC_WORKFLOW: "\u{1F9ED} Flujo de trabajo",
891
+ G_WF_1: "1. Pulsa \u23FA (o Ctrl+R) para empezar a grabar.",
892
+ G_WF_2: "2. Usa tu app como un usuario: clicks, formularios, navegaci\xF3n. Cada acci\xF3n se convierte en un comando Cypress.",
893
+ G_WF_3: "3. A\xF1ade verificaciones con Alt+click sobre lo que quieras comprobar.",
894
+ G_WF_4: "4. Pulsa \u23F9 para parar; ponle nombre y tags, y gu\xE1rdalo.",
895
+ G_WF_5: "5. En \u{1F4C1} Ficheros, inserta el test en un .cy.ts o l\xE1nzalo con el runner local.",
896
+ G_SEC_COVERED: "\u2705 Qu\xE9 cubre",
897
+ G_COV_1: "Clicks, doble-click, click derecho, inputs de texto, checkbox/radio, select y teclas Enter/Escape.",
898
+ G_COV_2: "Navegaci\xF3n SPA (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
899
+ G_COV_3: "Llamadas fetch/XHR (GET/POST/PUT) \u2192 cy.intercept + cy.wait, con validaci\xF3n de body opcional.",
900
+ G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
901
+ G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
902
+ G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
903
+ G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
904
+ G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
905
+ G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
906
+ G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
907
+ G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
908
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
909
+ G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
821
910
  }
822
911
  };
823
912
 
@@ -845,7 +934,10 @@ var I18N_EN = {
845
934
  SAVE_AND_EDIT: "\u{1F4DD} Save and edit",
846
935
  REMOVE_TAG_TITLE: "Remove",
847
936
  NOTES_LABEL: "Description / Notes (optional):",
848
- NOTES_PLACEHOLDER: "What this test validates, preconditions, etc."
937
+ NOTES_PLACEHOLDER: "What this test validates, preconditions, etc.",
938
+ TICKET_LABEL: "Ticket / Issue (optional):",
939
+ TICKET_PLACEHOLDER: "E.g.: PROJ-123, #42",
940
+ TICKET_WARN: "Invalid ID for the configured provider (will be saved anyway)"
849
941
  },
850
942
  TEST_EDITOR: {
851
943
  NO_TAGS: "No tags",
@@ -859,7 +951,11 @@ var I18N_EN = {
859
951
  COPY_ICPS_BTN: "\u{1F4CB} Copy interceptors",
860
952
  DEFAULT_DESCRIBE: "Test suite",
861
953
  NO_TESTS: "No saved tests",
862
- DELETE_TITLE: "Delete"
954
+ DELETE_TITLE: "Delete",
955
+ GROUP_BY_TICKET: "\u{1F3AB} Group by ticket",
956
+ UNGROUP_TICKET: "\u{1F3AB} Ungroup",
957
+ NO_TICKET: "No ticket",
958
+ TICKET_LINK_TITLE: "Open ticket"
863
959
  },
864
960
  CONFIG: {
865
961
  LANG_SECTION: "\u{1F310} Language",
@@ -867,6 +963,9 @@ var I18N_EN = {
867
963
  HTTP_SECTION: "\u26A1 Advanced HTTP",
868
964
  HTTP_TITLE: "Body validations",
869
965
  HTTP_SUB: "GET \u2192 response \xB7 POST/PUT \u2192 request",
966
+ FIXTURE_SECTION: "\u{1F9EA} HTTP Fixtures",
967
+ FIXTURE_TITLE: "Record responses as fixtures",
968
+ FIXTURE_SUB: "GET responses are saved to cypress/fixtures and stubbed via cy.intercept({ fixture }).",
870
969
  SELECTOR_SECTION: "\u{1F3AF} Selector strategy",
871
970
  SELECTOR_LABEL: "Preferred attribute for cy.get()",
872
971
  SELECTOR_OPT_DATACY: "data-cy (default)",
@@ -914,7 +1013,13 @@ var I18N_EN = {
914
1013
  WIDGET_POSITION_SECTION: "\u{1F9F2} Widget position",
915
1014
  WIDGET_POSITION_HINT: "Drag the record button to move it out of the way. Its position is remembered.",
916
1015
  WIDGET_POSITION_RESET_BTN: "\u21BA Reset position",
917
- WIDGET_POSITION_RESET_DONE: "\u2713 Position reset"
1016
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position reset",
1017
+ ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue tracker",
1018
+ ISSUE_TRACKER_TITLE: "Link tests to tickets",
1019
+ ISSUE_TRACKER_SUB: "Add a ticket ID when saving and generate a clickable link in the test list.",
1020
+ ISSUE_TRACKER_PROVIDER: "Provider",
1021
+ ISSUE_TRACKER_BASE_URL: "Base URL",
1022
+ ISSUE_TRACKER_BASE_URL_PH: "E.g.: https://myproject.atlassian.net"
918
1023
  },
919
1024
  SELECTOR_PICKER: {
920
1025
  TITLE: "Select a DOM element",
@@ -1003,6 +1108,8 @@ var I18N_EN = {
1003
1108
  FILE_EDITOR_TITLE: "\u270F\uFE0F File editor",
1004
1109
  FILE_SAVED_TOAST: "\u2713 File saved",
1005
1110
  FILE_SAVE_ERROR_TOAST: "Error saving the file",
1111
+ FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures written to cypress/fixtures",
1112
+ FIXTURES_NO_FOLDER_TOAST: "Could not write fixtures (configure the Cypress folder)",
1006
1113
  BTN_CONFIG: "Config",
1007
1114
  BTN_FILES: "Files",
1008
1115
  BTN_COMMANDS: "Commands",
@@ -1017,7 +1124,76 @@ var I18N_EN = {
1017
1124
  SESSION_RESUME_TEXT: "There is an unfinished active recording.",
1018
1125
  SESSION_RESUME_COUNT: "commands captured",
1019
1126
  SESSION_CONTINUE_BTN: "Keep recording",
1020
- SESSION_DISCARD_BTN: "Discard"
1127
+ SESSION_DISCARD_BTN: "Discard",
1128
+ BTN_HELP: "Help"
1129
+ },
1130
+ HELP: {
1131
+ TITLE: "\u2753 Help \u2014 Quick guide",
1132
+ INTRO: "Everything the recorder can do.",
1133
+ SEC_RECORDING: "\u23FA Recording",
1134
+ RECORDING_1: "Press \u23FA (or Ctrl+R) to start; \u23F9 to stop and save.",
1135
+ RECORDING_2: "Pause / resume with \u23F8 (or Ctrl+P).",
1136
+ SEC_SHORTCUTS: "\u2328\uFE0F Keyboard shortcuts",
1137
+ SC_RECORD: "Ctrl+R \u2014 Record / stop",
1138
+ SC_PAUSE: "Ctrl+P \u2014 Pause / resume",
1139
+ SC_TESTS: "Ctrl+1 \u2014 Saved tests panel",
1140
+ SC_COMMANDS: "Ctrl+2 \u2014 Commands panel",
1141
+ SC_CONFIG: "Ctrl+3 \u2014 Settings",
1142
+ SC_VISIBILITY: "Ctrl+Shift+E \u2014 Show / hide the widget",
1143
+ SC_HELP: "Ctrl+Shift+H \u2014 This help",
1144
+ SEC_CAPTURED: "\u{1F3AF} What gets recorded",
1145
+ CAP_CLICK: "Click \u2192 .click()",
1146
+ CAP_DBLCLICK: "Double-click \u2192 .dblclick()",
1147
+ CAP_RIGHTCLICK: "Right-click \u2192 .rightclick()",
1148
+ CAP_TYPE: "Type into a field \u2192 .clear().type(...)",
1149
+ CAP_CHECK: "Checkbox / radio \u2192 .check() / .uncheck()",
1150
+ CAP_SELECT: "<select> \u2192 .select(...)",
1151
+ CAP_KEYS: 'Enter / Escape \u2192 .type("{enter}") / ("{esc}")',
1152
+ CAP_ROUTE: "Route change (SPA) \u2192 cy.url().should(...)",
1153
+ CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
1154
+ SEC_ASSERT: "\u2705 Assertions",
1155
+ ASSERT_1: "Alt+click an element to verify it without running its action (text, value, checked or visible).",
1156
+ ASSERT_2: "Or use the assertion builder in the Commands panel.",
1157
+ SEC_PANELS: "\u{1F5C2} Panels",
1158
+ PANEL_TESTS: "\u{1F4CB} Tests \u2014 saved tests: filter by tag, copy, combine, export.",
1159
+ PANEL_COMMANDS: "\u2328\uFE0F Commands \u2014 current recording: reorder, delete, add assertions.",
1160
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 language, selector, HTTP, Cypress folder, data, visibility, position.",
1161
+ PANEL_FILES: "\u{1F4C1} Files \u2014 insert tests into your .cy.ts and launch the runner.",
1162
+ SEC_SELECTOR: "\u{1F50E} Selectors",
1163
+ SELECTOR_1: "Choose the preferred attribute (data-cy, data-testid, aria-label, id) in Config.",
1164
+ SELECTOR_2: "If a click has no reliable selector, a picker appears to choose the best ancestor.",
1165
+ SEC_DATA: "\u{1F4BE} Data",
1166
+ DATA_1: "Export tests to JSON (all / selection / by tags).",
1167
+ DATA_2: "Import a JSON: it merges into your data (nothing is wiped).",
1168
+ SEC_ADVANCED: "\u{1F680} Advanced",
1169
+ ADV_INVISIBLE: "Invisible mode: start-hidden + Ctrl+Shift+E to record in production unobtrusively.",
1170
+ ADV_RUNNER: "Local runner: npx lib-e2e-cypress-runner to run a spec headless from the editor.",
1171
+ ADV_CROSSAPP: "Cross-app: the recording survives switching micro-frontend (same origin).",
1172
+ ADV_DRAG: "Drag the \u23FA button to move the widget; its position is remembered.",
1173
+ CLOSE: "Close",
1174
+ TAB_REFERENCE: "Quick reference",
1175
+ TAB_GUIDE: "Usage guide",
1176
+ GUIDE_INTRO: "How to get the most out of the recorder, and what it does / doesn't cover.",
1177
+ G_SEC_WORKFLOW: "\u{1F9ED} Workflow",
1178
+ G_WF_1: "1. Press \u23FA (or Ctrl+R) to start recording.",
1179
+ G_WF_2: "2. Use your app like a user: clicks, forms, navigation. Each action becomes a Cypress command.",
1180
+ G_WF_3: "3. Add checks with Alt+click on whatever you want to verify.",
1181
+ G_WF_4: "4. Press \u23F9 to stop; give it a name and tags, and save it.",
1182
+ G_WF_5: "5. In \u{1F4C1} Files, insert the test into a .cy.ts or run it with the local runner.",
1183
+ G_SEC_COVERED: "\u2705 What it covers",
1184
+ G_COV_1: "Clicks, double-click, right-click, text inputs, checkbox/radio, select and Enter/Escape keys.",
1185
+ G_COV_2: "SPA navigation (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
1186
+ G_COV_3: "fetch/XHR calls (GET/POST/PUT) \u2192 cy.intercept + cy.wait, with optional body validation.",
1187
+ G_COV_4: "Reliable selector (data-cy/testid/aria/id) and a picker when none is obvious.",
1188
+ G_COV_5: "Automatic assertions (Alt+click) and the manual builder.",
1189
+ G_COV_6: "Continuity when switching micro-frontend (same origin), invisible mode and i18n.",
1190
+ G_SEC_NOTCOVERED: "\u{1F6AB} What it does NOT cover (yet)",
1191
+ G_NC_1: "Different origins / subdomains (same origin only).",
1192
+ G_NC_2: "File upload (selectFile), drag & drop, and hover.",
1193
+ G_NC_3: "The Tab key (Cypress can't .type() it).",
1194
+ G_NC_4: "DELETE requests (intentionally ignored).",
1195
+ G_NC_5: "Third-party iframes and Shadow DOM may not be captured.",
1196
+ G_NC_6: "Programmatic login generation (on the roadmap)."
1021
1197
  }
1022
1198
  };
1023
1199
 
@@ -1045,7 +1221,11 @@ var I18N_FR = {
1045
1221
  SAVE_AND_EDIT: "\u{1F4DD} Enregistrer et \xE9diter",
1046
1222
  REMOVE_TAG_TITLE: "Retirer",
1047
1223
  NOTES_LABEL: "Description / Notes (optionnel) :",
1048
- NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc."
1224
+ NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc.",
1225
+ TICKET_LABEL: "Ticket / Issue (optionnel) :",
1226
+ TICKET_PLACEHOLDER: "Ex : PROJ-123, #42",
1227
+ TICKET_WARN: "ID non valide pour le fournisseur configur\xE9 (sera enregistr\xE9 quand m\xEAme)"
1228
+ // TODO: translate
1049
1229
  },
1050
1230
  TEST_EDITOR: {
1051
1231
  NO_TAGS: "Sans \xE9tiquettes",
@@ -1059,7 +1239,11 @@ var I18N_FR = {
1059
1239
  COPY_ICPS_BTN: "\u{1F4CB} Copier les intercepteurs",
1060
1240
  DEFAULT_DESCRIBE: "Suite de tests",
1061
1241
  NO_TESTS: "Aucun test enregistr\xE9",
1062
- DELETE_TITLE: "Supprimer"
1242
+ DELETE_TITLE: "Supprimer",
1243
+ GROUP_BY_TICKET: "\u{1F3AB} Grouper par ticket",
1244
+ UNGROUP_TICKET: "\u{1F3AB} D\xE9grouper",
1245
+ NO_TICKET: "Sans ticket",
1246
+ TICKET_LINK_TITLE: "Ouvrir le ticket"
1063
1247
  },
1064
1248
  CONFIG: {
1065
1249
  LANG_SECTION: "\u{1F310} Langue",
@@ -1067,6 +1251,9 @@ var I18N_FR = {
1067
1251
  HTTP_SECTION: "\u26A1 HTTP Avanc\xE9",
1068
1252
  HTTP_TITLE: "Validations du corps",
1069
1253
  HTTP_SUB: "GET \u2192 r\xE9ponse \xB7 POST/PUT \u2192 requ\xEAte",
1254
+ FIXTURE_SECTION: "\u{1F9EA} Fixtures HTTP",
1255
+ FIXTURE_TITLE: "Enregistrer les r\xE9ponses comme fixtures",
1256
+ FIXTURE_SUB: "Les GET sont enregistr\xE9es dans cypress/fixtures et stub\xE9es via cy.intercept({ fixture }).",
1070
1257
  SELECTOR_SECTION: "\u{1F3AF} Strat\xE9gie de s\xE9lecteur",
1071
1258
  SELECTOR_LABEL: "Attribut pr\xE9f\xE9r\xE9 pour cy.get()",
1072
1259
  SELECTOR_OPT_DATACY: "data-cy (par d\xE9faut)",
@@ -1114,7 +1301,13 @@ var I18N_FR = {
1114
1301
  WIDGET_POSITION_SECTION: "\u{1F9F2} Position du widget",
1115
1302
  WIDGET_POSITION_HINT: "Fais glisser le bouton d'enregistrement pour le d\xE9placer. Sa position est m\xE9moris\xE9e.",
1116
1303
  WIDGET_POSITION_RESET_BTN: "\u21BA R\xE9initialiser la position",
1117
- WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e"
1304
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e",
1305
+ ISSUE_TRACKER_SECTION: "\u{1F3AB} Gestionnaire de tickets",
1306
+ ISSUE_TRACKER_TITLE: "Lier les tests aux tickets",
1307
+ ISSUE_TRACKER_SUB: "Ajoutez un ID de ticket lors de l'enregistrement et g\xE9n\xE9rez un lien cliquable.",
1308
+ ISSUE_TRACKER_PROVIDER: "Fournisseur",
1309
+ ISSUE_TRACKER_BASE_URL: "URL de base",
1310
+ ISSUE_TRACKER_BASE_URL_PH: "Ex : https://monprojet.atlassian.net"
1118
1311
  },
1119
1312
  SELECTOR_PICKER: {
1120
1313
  TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
@@ -1203,6 +1396,8 @@ var I18N_FR = {
1203
1396
  FILE_EDITOR_TITLE: "\u270F\uFE0F \xC9diteur de fichier",
1204
1397
  FILE_SAVED_TOAST: "\u2713 Fichier enregistr\xE9",
1205
1398
  FILE_SAVE_ERROR_TOAST: "Erreur lors de l'enregistrement",
1399
+ FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures \xE9crites dans cypress/fixtures",
1400
+ FIXTURES_NO_FOLDER_TOAST: "Impossible d'\xE9crire les fixtures (configure le dossier Cypress)",
1206
1401
  BTN_CONFIG: "Config",
1207
1402
  BTN_FILES: "Fichiers",
1208
1403
  BTN_COMMANDS: "Commandes",
@@ -1217,7 +1412,77 @@ var I18N_FR = {
1217
1412
  SESSION_RESUME_TEXT: "Un enregistrement actif est inachev\xE9.",
1218
1413
  SESSION_RESUME_COUNT: "commandes captur\xE9es",
1219
1414
  SESSION_CONTINUE_BTN: "Continuer l'enregistrement",
1220
- SESSION_DISCARD_BTN: "Ignorer"
1415
+ SESSION_DISCARD_BTN: "Ignorer",
1416
+ BTN_HELP: "Aide"
1417
+ },
1418
+ HELP: {
1419
+ TITLE: "\u2753 Aide \u2014 Guide rapide",
1420
+ INTRO: "Tout ce que l'enregistreur sait faire.",
1421
+ SEC_RECORDING: "\u23FA Enregistrer",
1422
+ RECORDING_1: "Appuie sur \u23FA (ou Ctrl+R) pour commencer ; \u23F9 pour arr\xEAter et enregistrer.",
1423
+ RECORDING_2: "Pause / reprise avec \u23F8 (ou Ctrl+P).",
1424
+ SEC_SHORTCUTS: "\u2328\uFE0F Raccourcis clavier",
1425
+ SC_RECORD: "Ctrl+R \u2014 Enregistrer / arr\xEAter",
1426
+ SC_PAUSE: "Ctrl+P \u2014 Pause / reprise",
1427
+ SC_TESTS: "Ctrl+1 \u2014 Panneau des tests enregistr\xE9s",
1428
+ SC_COMMANDS: "Ctrl+2 \u2014 Panneau des commandes",
1429
+ SC_CONFIG: "Ctrl+3 \u2014 Param\xE8tres",
1430
+ SC_VISIBILITY: "Ctrl+Shift+E \u2014 Afficher / masquer le widget",
1431
+ SC_HELP: "Ctrl+Shift+H \u2014 Cette aide",
1432
+ SEC_CAPTURED: "\u{1F3AF} Ce qui est enregistr\xE9",
1433
+ CAP_CLICK: "Clic \u2192 .click()",
1434
+ CAP_DBLCLICK: "Double-clic \u2192 .dblclick()",
1435
+ CAP_RIGHTCLICK: "Clic droit \u2192 .rightclick()",
1436
+ CAP_TYPE: "Saisie dans un champ \u2192 .clear().type(...)",
1437
+ CAP_CHECK: "Case \xE0 cocher / radio \u2192 .check() / .uncheck()",
1438
+ CAP_SELECT: "<select> \u2192 .select(...)",
1439
+ CAP_KEYS: 'Entr\xE9e / \xC9chap \u2192 .type("{enter}") / ("{esc}")',
1440
+ CAP_ROUTE: "Changement de route (SPA) \u2192 cy.url().should(...)",
1441
+ CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
1442
+ SEC_ASSERT: "\u2705 Assertions",
1443
+ ASSERT_1: "Alt+clic sur un \xE9l\xE9ment pour le v\xE9rifier sans ex\xE9cuter son action (texte, valeur, coch\xE9 ou visible).",
1444
+ ASSERT_2: "Ou utilise le constructeur d'assertions dans le panneau Commandes.",
1445
+ SEC_PANELS: "\u{1F5C2} Panneaux",
1446
+ PANEL_TESTS: "\u{1F4CB} Tests \u2014 enregistr\xE9s : filtrer par tag, copier, combiner, exporter.",
1447
+ PANEL_COMMANDS: "\u2328\uFE0F Commandes \u2014 enregistrement en cours : r\xE9ordonner, supprimer, ajouter des assertions.",
1448
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 langue, s\xE9lecteur, HTTP, dossier Cypress, donn\xE9es, visibilit\xE9, position.",
1449
+ PANEL_FILES: "\u{1F4C1} Fichiers \u2014 ins\xE8re des tests dans tes .cy.ts et lance le runner.",
1450
+ SEC_SELECTOR: "\u{1F50E} S\xE9lecteurs",
1451
+ SELECTOR_1: "Choisis l'attribut pr\xE9f\xE9r\xE9 (data-cy, data-testid, aria-label, id) dans Config.",
1452
+ SELECTOR_2: "Si un clic n'a pas de s\xE9lecteur fiable, un s\xE9lecteur appara\xEEt pour choisir le meilleur anc\xEAtre.",
1453
+ SEC_DATA: "\u{1F4BE} Donn\xE9es",
1454
+ DATA_1: "Exporte les tests en JSON (tout / s\xE9lection / par tags).",
1455
+ DATA_2: "Importe un JSON : il fusionne avec tes donn\xE9es (rien n'est effac\xE9).",
1456
+ SEC_ADVANCED: "\u{1F680} Avanc\xE9",
1457
+ ADV_INVISIBLE: "Mode invisible : start-hidden + Ctrl+Shift+E pour enregistrer en production discr\xE8tement.",
1458
+ ADV_RUNNER: "Runner local : npx lib-e2e-cypress-runner pour lancer un spec headless depuis l'\xE9diteur.",
1459
+ ADV_CROSSAPP: "Cross-app : l'enregistrement survit au changement de micro-frontend (m\xEAme origine).",
1460
+ ADV_DRAG: "Fais glisser le bouton \u23FA pour d\xE9placer le widget ; sa position est m\xE9moris\xE9e.",
1461
+ CLOSE: "Fermer",
1462
+ TAB_REFERENCE: "R\xE9f\xE9rence rapide",
1463
+ TAB_GUIDE: "Guide d'utilisation",
1464
+ // TODO: translate (fr) — usage guide body kept in Spanish for now.
1465
+ GUIDE_INTRO: "C\xF3mo sacarle partido al grabador, qu\xE9 cubre y qu\xE9 no.",
1466
+ G_SEC_WORKFLOW: "\u{1F9ED} Flujo de trabajo",
1467
+ G_WF_1: "1. Pulsa \u23FA (o Ctrl+R) para empezar a grabar.",
1468
+ G_WF_2: "2. Usa tu app como un usuario: clicks, formularios, navegaci\xF3n. Cada acci\xF3n se convierte en un comando Cypress.",
1469
+ G_WF_3: "3. A\xF1ade verificaciones con Alt+click sobre lo que quieras comprobar.",
1470
+ G_WF_4: "4. Pulsa \u23F9 para parar; ponle nombre y tags, y gu\xE1rdalo.",
1471
+ G_WF_5: "5. En \u{1F4C1} Ficheros, inserta el test en un .cy.ts o l\xE1nzalo con el runner local.",
1472
+ G_SEC_COVERED: "\u2705 Qu\xE9 cubre",
1473
+ G_COV_1: "Clicks, doble-click, click derecho, inputs de texto, checkbox/radio, select y teclas Enter/Escape.",
1474
+ G_COV_2: "Navegaci\xF3n SPA (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
1475
+ G_COV_3: "Llamadas fetch/XHR (GET/POST/PUT) \u2192 cy.intercept + cy.wait, con validaci\xF3n de body opcional.",
1476
+ G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
1477
+ G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
1478
+ G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
1479
+ G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
1480
+ G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
1481
+ G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
1482
+ G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
1483
+ G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
1484
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
1485
+ G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
1221
1486
  }
1222
1487
  };
1223
1488
 
@@ -1245,7 +1510,11 @@ var I18N_IT = {
1245
1510
  SAVE_AND_EDIT: "\u{1F4DD} Salva e modifica",
1246
1511
  REMOVE_TAG_TITLE: "Rimuovi",
1247
1512
  NOTES_LABEL: "Descrizione / Note (opzionale):",
1248
- NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc."
1513
+ NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc.",
1514
+ TICKET_LABEL: "Ticket / Issue (opzionale):",
1515
+ TICKET_PLACEHOLDER: "Es: PROJ-123, #42",
1516
+ TICKET_WARN: "ID non valido per il provider configurato (verr\xE0 salvato comunque)"
1517
+ // TODO: translate
1249
1518
  },
1250
1519
  TEST_EDITOR: {
1251
1520
  NO_TAGS: "Nessuna etichetta",
@@ -1259,7 +1528,11 @@ var I18N_IT = {
1259
1528
  COPY_ICPS_BTN: "\u{1F4CB} Copia interceptor",
1260
1529
  DEFAULT_DESCRIBE: "Suite di test",
1261
1530
  NO_TESTS: "Nessun test salvato",
1262
- DELETE_TITLE: "Elimina"
1531
+ DELETE_TITLE: "Elimina",
1532
+ GROUP_BY_TICKET: "\u{1F3AB} Raggruppa per ticket",
1533
+ UNGROUP_TICKET: "\u{1F3AB} Separa",
1534
+ NO_TICKET: "Senza ticket",
1535
+ TICKET_LINK_TITLE: "Apri ticket"
1263
1536
  },
1264
1537
  CONFIG: {
1265
1538
  LANG_SECTION: "\u{1F310} Lingua",
@@ -1267,6 +1540,9 @@ var I18N_IT = {
1267
1540
  HTTP_SECTION: "\u26A1 HTTP Avanzato",
1268
1541
  HTTP_TITLE: "Validazioni del body",
1269
1542
  HTTP_SUB: "GET \u2192 risposta \xB7 POST/PUT \u2192 richiesta",
1543
+ FIXTURE_SECTION: "\u{1F9EA} Fixture HTTP",
1544
+ FIXTURE_TITLE: "Registra le risposte come fixture",
1545
+ FIXTURE_SUB: "Le GET vengono salvate in cypress/fixtures e stubbate con cy.intercept({ fixture }).",
1270
1546
  SELECTOR_SECTION: "\u{1F3AF} Strategia selettore",
1271
1547
  SELECTOR_LABEL: "Attributo preferito per cy.get()",
1272
1548
  SELECTOR_OPT_DATACY: "data-cy (predefinito)",
@@ -1314,7 +1590,13 @@ var I18N_IT = {
1314
1590
  WIDGET_POSITION_SECTION: "\u{1F9F2} Posizione del widget",
1315
1591
  WIDGET_POSITION_HINT: "Trascina il pulsante di registrazione per spostarlo. La posizione viene ricordata.",
1316
1592
  WIDGET_POSITION_RESET_BTN: "\u21BA Reimposta posizione",
1317
- WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata"
1593
+ WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata",
1594
+ ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue tracker",
1595
+ ISSUE_TRACKER_TITLE: "Collega i test ai ticket",
1596
+ ISSUE_TRACKER_SUB: "Aggiungi un ID ticket al salvataggio e genera un link cliccabile nell'elenco.",
1597
+ ISSUE_TRACKER_PROVIDER: "Provider",
1598
+ ISSUE_TRACKER_BASE_URL: "URL base",
1599
+ ISSUE_TRACKER_BASE_URL_PH: "Es: https://mioprogetto.atlassian.net"
1318
1600
  },
1319
1601
  SELECTOR_PICKER: {
1320
1602
  TITLE: "Seleziona un elemento DOM",
@@ -1403,6 +1685,8 @@ var I18N_IT = {
1403
1685
  FILE_EDITOR_TITLE: "\u270F\uFE0F Editor di file",
1404
1686
  FILE_SAVED_TOAST: "\u2713 File salvato",
1405
1687
  FILE_SAVE_ERROR_TOAST: "Errore durante il salvataggio",
1688
+ FIXTURES_WRITTEN_TOAST: "\u2713 Fixture scritte in cypress/fixtures",
1689
+ FIXTURES_NO_FOLDER_TOAST: "Impossibile scrivere le fixture (configura la cartella Cypress)",
1406
1690
  BTN_CONFIG: "Config",
1407
1691
  BTN_FILES: "File",
1408
1692
  BTN_COMMANDS: "Comandi",
@@ -1417,7 +1701,77 @@ var I18N_IT = {
1417
1701
  SESSION_RESUME_TEXT: "C'\xE8 una registrazione attiva non terminata.",
1418
1702
  SESSION_RESUME_COUNT: "comandi catturati",
1419
1703
  SESSION_CONTINUE_BTN: "Continua a registrare",
1420
- SESSION_DISCARD_BTN: "Annulla"
1704
+ SESSION_DISCARD_BTN: "Annulla",
1705
+ BTN_HELP: "Aiuto"
1706
+ },
1707
+ HELP: {
1708
+ TITLE: "\u2753 Aiuto \u2014 Guida rapida",
1709
+ INTRO: "Tutto ci\xF2 che il registratore sa fare.",
1710
+ SEC_RECORDING: "\u23FA Registrare",
1711
+ RECORDING_1: "Premi \u23FA (o Ctrl+R) per iniziare; \u23F9 per fermare e salvare.",
1712
+ RECORDING_2: "Pausa / ripresa con \u23F8 (o Ctrl+P).",
1713
+ SEC_SHORTCUTS: "\u2328\uFE0F Scorciatoie da tastiera",
1714
+ SC_RECORD: "Ctrl+R \u2014 Registra / ferma",
1715
+ SC_PAUSE: "Ctrl+P \u2014 Pausa / ripresa",
1716
+ SC_TESTS: "Ctrl+1 \u2014 Pannello test salvati",
1717
+ SC_COMMANDS: "Ctrl+2 \u2014 Pannello comandi",
1718
+ SC_CONFIG: "Ctrl+3 \u2014 Impostazioni",
1719
+ SC_VISIBILITY: "Ctrl+Shift+E \u2014 Mostra / nascondi il widget",
1720
+ SC_HELP: "Ctrl+Shift+H \u2014 Questo aiuto",
1721
+ SEC_CAPTURED: "\u{1F3AF} Cosa viene registrato",
1722
+ CAP_CLICK: "Click \u2192 .click()",
1723
+ CAP_DBLCLICK: "Doppio click \u2192 .dblclick()",
1724
+ CAP_RIGHTCLICK: "Click destro \u2192 .rightclick()",
1725
+ CAP_TYPE: "Digitare in un campo \u2192 .clear().type(...)",
1726
+ CAP_CHECK: "Checkbox / radio \u2192 .check() / .uncheck()",
1727
+ CAP_SELECT: "<select> \u2192 .select(...)",
1728
+ CAP_KEYS: 'Invio / Esc \u2192 .type("{enter}") / ("{esc}")',
1729
+ CAP_ROUTE: "Cambio di rotta (SPA) \u2192 cy.url().should(...)",
1730
+ CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
1731
+ SEC_ASSERT: "\u2705 Asserzioni",
1732
+ ASSERT_1: "Alt+click su un elemento per verificarlo senza eseguire la sua azione (testo, valore, checked o visibile).",
1733
+ ASSERT_2: "Oppure usa il costruttore di asserzioni nel pannello Comandi.",
1734
+ SEC_PANELS: "\u{1F5C2} Pannelli",
1735
+ PANEL_TESTS: "\u{1F4CB} Test \u2014 salvati: filtra per tag, copia, combina, esporta.",
1736
+ PANEL_COMMANDS: "\u2328\uFE0F Comandi \u2014 registrazione corrente: riordina, elimina, aggiungi asserzioni.",
1737
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 lingua, selettore, HTTP, cartella Cypress, dati, visibilit\xE0, posizione.",
1738
+ PANEL_FILES: "\u{1F4C1} File \u2014 inserisci test nei tuoi .cy.ts e avvia il runner.",
1739
+ SEC_SELECTOR: "\u{1F50E} Selettori",
1740
+ SELECTOR_1: "Scegli l'attributo preferito (data-cy, data-testid, aria-label, id) in Config.",
1741
+ SELECTOR_2: "Se un click non ha un selettore affidabile, appare un picker per scegliere l'antenato migliore.",
1742
+ SEC_DATA: "\u{1F4BE} Dati",
1743
+ DATA_1: "Esporta i test in JSON (tutto / selezione / per tag).",
1744
+ DATA_2: "Importa un JSON: si fonde con i tuoi dati (non si cancella nulla).",
1745
+ SEC_ADVANCED: "\u{1F680} Avanzato",
1746
+ ADV_INVISIBLE: "Modalit\xE0 invisibile: start-hidden + Ctrl+Shift+E per registrare in produzione senza disturbare.",
1747
+ ADV_RUNNER: "Runner locale: npx lib-e2e-cypress-runner per eseguire uno spec headless dall'editor.",
1748
+ ADV_CROSSAPP: "Cross-app: la registrazione sopravvive al cambio di micro-frontend (stessa origine).",
1749
+ ADV_DRAG: "Trascina il pulsante \u23FA per spostare il widget; la posizione viene ricordata.",
1750
+ CLOSE: "Chiudi",
1751
+ TAB_REFERENCE: "Riferimento rapido",
1752
+ TAB_GUIDE: "Guida all'uso",
1753
+ // TODO: translate (it) — usage guide body kept in Spanish for now.
1754
+ GUIDE_INTRO: "C\xF3mo sacarle partido al grabador, qu\xE9 cubre y qu\xE9 no.",
1755
+ G_SEC_WORKFLOW: "\u{1F9ED} Flujo de trabajo",
1756
+ G_WF_1: "1. Pulsa \u23FA (o Ctrl+R) para empezar a grabar.",
1757
+ G_WF_2: "2. Usa tu app como un usuario: clicks, formularios, navegaci\xF3n. Cada acci\xF3n se convierte en un comando Cypress.",
1758
+ G_WF_3: "3. A\xF1ade verificaciones con Alt+click sobre lo que quieras comprobar.",
1759
+ G_WF_4: "4. Pulsa \u23F9 para parar; ponle nombre y tags, y gu\xE1rdalo.",
1760
+ G_WF_5: "5. En \u{1F4C1} Ficheros, inserta el test en un .cy.ts o l\xE1nzalo con el runner local.",
1761
+ G_SEC_COVERED: "\u2705 Qu\xE9 cubre",
1762
+ G_COV_1: "Clicks, doble-click, click derecho, inputs de texto, checkbox/radio, select y teclas Enter/Escape.",
1763
+ G_COV_2: "Navegaci\xF3n SPA (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
1764
+ G_COV_3: "Llamadas fetch/XHR (GET/POST/PUT) \u2192 cy.intercept + cy.wait, con validaci\xF3n de body opcional.",
1765
+ G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
1766
+ G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
1767
+ G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
1768
+ G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
1769
+ G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
1770
+ G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
1771
+ G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
1772
+ G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
1773
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
1774
+ G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
1421
1775
  }
1422
1776
  };
1423
1777
 
@@ -1445,7 +1799,11 @@ var I18N_DE = {
1445
1799
  SAVE_AND_EDIT: "\u{1F4DD} Speichern und bearbeiten",
1446
1800
  REMOVE_TAG_TITLE: "Entfernen",
1447
1801
  NOTES_LABEL: "Beschreibung / Notizen (optional):",
1448
- NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw."
1802
+ NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw.",
1803
+ TICKET_LABEL: "Ticket / Issue (optional):",
1804
+ TICKET_PLACEHOLDER: "Z. B.: PROJ-123, #42",
1805
+ TICKET_WARN: "Ung\xFCltige ID f\xFCr den konfigurierten Anbieter (wird trotzdem gespeichert)"
1806
+ // TODO: translate
1449
1807
  },
1450
1808
  TEST_EDITOR: {
1451
1809
  NO_TAGS: "Keine Tags",
@@ -1459,7 +1817,11 @@ var I18N_DE = {
1459
1817
  COPY_ICPS_BTN: "\u{1F4CB} Interceptors kopieren",
1460
1818
  DEFAULT_DESCRIBE: "Test-Suite",
1461
1819
  NO_TESTS: "Keine gespeicherten Tests",
1462
- DELETE_TITLE: "L\xF6schen"
1820
+ DELETE_TITLE: "L\xF6schen",
1821
+ GROUP_BY_TICKET: "\u{1F3AB} Nach Ticket gruppieren",
1822
+ UNGROUP_TICKET: "\u{1F3AB} Gruppierung aufheben",
1823
+ NO_TICKET: "Kein Ticket",
1824
+ TICKET_LINK_TITLE: "Ticket \xF6ffnen"
1463
1825
  },
1464
1826
  CONFIG: {
1465
1827
  LANG_SECTION: "\u{1F310} Sprache",
@@ -1467,6 +1829,9 @@ var I18N_DE = {
1467
1829
  HTTP_SECTION: "\u26A1 Erweitertes HTTP",
1468
1830
  HTTP_TITLE: "Body-Validierungen",
1469
1831
  HTTP_SUB: "GET \u2192 Antwort \xB7 POST/PUT \u2192 Anfrage",
1832
+ FIXTURE_SECTION: "\u{1F9EA} HTTP-Fixtures",
1833
+ FIXTURE_TITLE: "Antworten als Fixtures aufzeichnen",
1834
+ FIXTURE_SUB: "GET-Antworten werden in cypress/fixtures gespeichert und mit cy.intercept({ fixture }) gestubbt.",
1470
1835
  SELECTOR_SECTION: "\u{1F3AF} Selector-Strategie",
1471
1836
  SELECTOR_LABEL: "Bevorzugtes Attribut f\xFCr cy.get()",
1472
1837
  SELECTOR_OPT_DATACY: "data-cy (Standard)",
@@ -1514,7 +1879,13 @@ var I18N_DE = {
1514
1879
  WIDGET_POSITION_SECTION: "\u{1F9F2} Widget-Position",
1515
1880
  WIDGET_POSITION_HINT: "Ziehe den Aufnahme-Button, um ihn aus dem Weg zu schieben. Die Position wird gespeichert.",
1516
1881
  WIDGET_POSITION_RESET_BTN: "\u21BA Position zur\xFCcksetzen",
1517
- WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt"
1882
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt",
1883
+ ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue-Tracker",
1884
+ ISSUE_TRACKER_TITLE: "Tests mit Tickets verkn\xFCpfen",
1885
+ ISSUE_TRACKER_SUB: "F\xFCge beim Speichern eine Ticket-ID hinzu und erstelle einen klickbaren Link.",
1886
+ ISSUE_TRACKER_PROVIDER: "Anbieter",
1887
+ ISSUE_TRACKER_BASE_URL: "Basis-URL",
1888
+ ISSUE_TRACKER_BASE_URL_PH: "Z. B.: https://meinprojekt.atlassian.net"
1518
1889
  },
1519
1890
  SELECTOR_PICKER: {
1520
1891
  TITLE: "DOM-Element ausw\xE4hlen",
@@ -1603,6 +1974,8 @@ var I18N_DE = {
1603
1974
  FILE_EDITOR_TITLE: "\u270F\uFE0F Datei-Editor",
1604
1975
  FILE_SAVED_TOAST: "\u2713 Datei gespeichert",
1605
1976
  FILE_SAVE_ERROR_TOAST: "Fehler beim Speichern",
1977
+ FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures in cypress/fixtures geschrieben",
1978
+ FIXTURES_NO_FOLDER_TOAST: "Fixtures konnten nicht geschrieben werden (Cypress-Ordner konfigurieren)",
1606
1979
  BTN_CONFIG: "Config",
1607
1980
  BTN_FILES: "Dateien",
1608
1981
  BTN_COMMANDS: "Befehle",
@@ -1617,7 +1990,77 @@ var I18N_DE = {
1617
1990
  SESSION_RESUME_TEXT: "Es gibt eine nicht abgeschlossene aktive Aufzeichnung.",
1618
1991
  SESSION_RESUME_COUNT: "erfasste Befehle",
1619
1992
  SESSION_CONTINUE_BTN: "Weiter aufzeichnen",
1620
- SESSION_DISCARD_BTN: "Verwerfen"
1993
+ SESSION_DISCARD_BTN: "Verwerfen",
1994
+ BTN_HELP: "Hilfe"
1995
+ },
1996
+ HELP: {
1997
+ TITLE: "\u2753 Hilfe \u2014 Kurzanleitung",
1998
+ INTRO: "Alles, was der Recorder kann.",
1999
+ SEC_RECORDING: "\u23FA Aufzeichnen",
2000
+ RECORDING_1: "Dr\xFCcke \u23FA (oder Ctrl+R) zum Starten; \u23F9 zum Stoppen und Speichern.",
2001
+ RECORDING_2: "Pause / Fortsetzen mit \u23F8 (oder Ctrl+P).",
2002
+ SEC_SHORTCUTS: "\u2328\uFE0F Tastenk\xFCrzel",
2003
+ SC_RECORD: "Ctrl+R \u2014 Aufzeichnen / stoppen",
2004
+ SC_PAUSE: "Ctrl+P \u2014 Pausieren / fortsetzen",
2005
+ SC_TESTS: "Ctrl+1 \u2014 Panel gespeicherte Tests",
2006
+ SC_COMMANDS: "Ctrl+2 \u2014 Befehle-Panel",
2007
+ SC_CONFIG: "Ctrl+3 \u2014 Einstellungen",
2008
+ SC_VISIBILITY: "Ctrl+Shift+E \u2014 Widget ein-/ausblenden",
2009
+ SC_HELP: "Ctrl+Shift+H \u2014 Diese Hilfe",
2010
+ SEC_CAPTURED: "\u{1F3AF} Was aufgezeichnet wird",
2011
+ CAP_CLICK: "Klick \u2192 .click()",
2012
+ CAP_DBLCLICK: "Doppelklick \u2192 .dblclick()",
2013
+ CAP_RIGHTCLICK: "Rechtsklick \u2192 .rightclick()",
2014
+ CAP_TYPE: "In ein Feld tippen \u2192 .clear().type(...)",
2015
+ CAP_CHECK: "Checkbox / Radio \u2192 .check() / .uncheck()",
2016
+ CAP_SELECT: "<select> \u2192 .select(...)",
2017
+ CAP_KEYS: 'Enter / Escape \u2192 .type("{enter}") / ("{esc}")',
2018
+ CAP_ROUTE: "Routenwechsel (SPA) \u2192 cy.url().should(...)",
2019
+ CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
2020
+ SEC_ASSERT: "\u2705 Assertions",
2021
+ ASSERT_1: "Alt+Klick auf ein Element, um es zu pr\xFCfen, ohne seine Aktion auszul\xF6sen (Text, Wert, checked oder sichtbar).",
2022
+ ASSERT_2: "Oder nutze den Assertion-Builder im Befehle-Panel.",
2023
+ SEC_PANELS: "\u{1F5C2} Panels",
2024
+ PANEL_TESTS: "\u{1F4CB} Tests \u2014 gespeichert: nach Tag filtern, kopieren, kombinieren, exportieren.",
2025
+ PANEL_COMMANDS: "\u2328\uFE0F Befehle \u2014 aktuelle Aufzeichnung: umsortieren, l\xF6schen, Assertions hinzuf\xFCgen.",
2026
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 Sprache, Selektor, HTTP, Cypress-Ordner, Daten, Sichtbarkeit, Position.",
2027
+ PANEL_FILES: "\u{1F4C1} Dateien \u2014 Tests in deine .cy.ts einf\xFCgen und den Runner starten.",
2028
+ SEC_SELECTOR: "\u{1F50E} Selektoren",
2029
+ SELECTOR_1: "W\xE4hle das bevorzugte Attribut (data-cy, data-testid, aria-label, id) unter Config.",
2030
+ SELECTOR_2: "Hat ein Klick keinen zuverl\xE4ssigen Selektor, erscheint ein Picker zur Wahl des besten Vorfahren.",
2031
+ SEC_DATA: "\u{1F4BE} Daten",
2032
+ DATA_1: "Tests als JSON exportieren (alle / Auswahl / nach Tags).",
2033
+ DATA_2: "JSON importieren: wird zu deinen Daten hinzugef\xFCgt (nichts wird gel\xF6scht).",
2034
+ SEC_ADVANCED: "\u{1F680} Erweitert",
2035
+ ADV_INVISIBLE: "Unsichtbarer Modus: start-hidden + Ctrl+Shift+E, um in Produktion unauff\xE4llig aufzuzeichnen.",
2036
+ ADV_RUNNER: "Lokaler Runner: npx lib-e2e-cypress-runner, um einen Spec headless aus dem Editor zu starten.",
2037
+ ADV_CROSSAPP: "Cross-App: die Aufzeichnung \xFCbersteht den Wechsel des Micro-Frontends (gleicher Origin).",
2038
+ ADV_DRAG: "Ziehe den \u23FA-Button, um das Widget zu verschieben; die Position wird gemerkt.",
2039
+ CLOSE: "Schlie\xDFen",
2040
+ TAB_REFERENCE: "Kurzreferenz",
2041
+ TAB_GUIDE: "Nutzungsleitfaden",
2042
+ // TODO: translate (de) — usage guide body kept in Spanish for now.
2043
+ GUIDE_INTRO: "C\xF3mo sacarle partido al grabador, qu\xE9 cubre y qu\xE9 no.",
2044
+ G_SEC_WORKFLOW: "\u{1F9ED} Flujo de trabajo",
2045
+ G_WF_1: "1. Pulsa \u23FA (o Ctrl+R) para empezar a grabar.",
2046
+ G_WF_2: "2. Usa tu app como un usuario: clicks, formularios, navegaci\xF3n. Cada acci\xF3n se convierte en un comando Cypress.",
2047
+ G_WF_3: "3. A\xF1ade verificaciones con Alt+click sobre lo que quieras comprobar.",
2048
+ G_WF_4: "4. Pulsa \u23F9 para parar; ponle nombre y tags, y gu\xE1rdalo.",
2049
+ G_WF_5: "5. En \u{1F4C1} Ficheros, inserta el test en un .cy.ts o l\xE1nzalo con el runner local.",
2050
+ G_SEC_COVERED: "\u2705 Qu\xE9 cubre",
2051
+ G_COV_1: "Clicks, doble-click, click derecho, inputs de texto, checkbox/radio, select y teclas Enter/Escape.",
2052
+ G_COV_2: "Navegaci\xF3n SPA (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
2053
+ G_COV_3: "Llamadas fetch/XHR (GET/POST/PUT) \u2192 cy.intercept + cy.wait, con validaci\xF3n de body opcional.",
2054
+ G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
2055
+ G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
2056
+ G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
2057
+ G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
2058
+ G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
2059
+ G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
2060
+ G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
2061
+ G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
2062
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
2063
+ G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
1621
2064
  }
1622
2065
  };
1623
2066
 
@@ -1750,6 +2193,32 @@ var advancedTestTransformationService = new AdvancedTestTransformationService();
1750
2193
 
1751
2194
  // src/services/recording.service.ts
1752
2195
  init_selector_quality_utils();
2196
+
2197
+ // src/utils/assertion.utils.ts
2198
+ var MAX_TEXT_LENGTH = 60;
2199
+ function inferAssertionCommand(el, selector) {
2200
+ const should = (body) => `cy.get('${selector}').should(${body})`;
2201
+ const tag = el.tagName.toLowerCase();
2202
+ if (tag === "input") {
2203
+ const input = el;
2204
+ const type = (input.getAttribute("type") ?? "text").toLowerCase();
2205
+ if (type === "checkbox" || type === "radio") {
2206
+ return should(`'${input.checked ? "be.checked" : "not.be.checked"}'`);
2207
+ }
2208
+ return input.value ? should(`'have.value', '${escapeSingleQuotes(input.value)}'`) : should(`'be.visible'`);
2209
+ }
2210
+ if (tag === "textarea" || tag === "select") {
2211
+ const value = el.value;
2212
+ return value ? should(`'have.value', '${escapeSingleQuotes(value)}'`) : should(`'be.visible'`);
2213
+ }
2214
+ const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
2215
+ if (text && text.length <= MAX_TEXT_LENGTH) {
2216
+ return should(`'contain.text', '${escapeSingleQuotes(text)}'`);
2217
+ }
2218
+ return should(`'be.visible'`);
2219
+ }
2220
+
2221
+ // src/services/recording.service.ts
1753
2222
  var OWN_SELECTOR = '[data-cy="lib-e2e-cypress-for-dummys"]';
1754
2223
  function createSessionId() {
1755
2224
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -1764,6 +2233,7 @@ var RecordingService = class {
1764
2233
  isPaused$ = new Subject(false);
1765
2234
  selectorNotFound$ = new Subject(null);
1766
2235
  inputDebounceTimers = /* @__PURE__ */ new Map();
2236
+ fixtures = /* @__PURE__ */ new Map();
1767
2237
  abort = new AbortController();
1768
2238
  selectorStrategy = "data-cy";
1769
2239
  /** Stable id of the current live session (null when none has started). */
@@ -1773,13 +2243,18 @@ var RecordingService = class {
1773
2243
  origPushState = history.pushState.bind(history);
1774
2244
  origReplaceState = history.replaceState.bind(history);
1775
2245
  constructor() {
2246
+ this.listenToAssertClicks();
1776
2247
  this.listenToClicks();
2248
+ this.listenToDoubleClicks();
2249
+ this.listenToContextMenu();
1777
2250
  this.listenToInput();
1778
2251
  this.listenToSelect();
2252
+ this.listenToKeys();
1779
2253
  this.listenToRouteChanges();
1780
2254
  }
1781
2255
  // ── Public API ────────────────────────────────────────────────────────────
1782
2256
  startRecording() {
2257
+ this.clearCommands();
1783
2258
  this.sessionId = createSessionId();
1784
2259
  this.startedAt = Date.now();
1785
2260
  this.isPaused$.next(false);
@@ -1866,17 +2341,27 @@ var RecordingService = class {
1866
2341
  if (index < 0 || index >= ints.length) return;
1867
2342
  this.interceptors$.next([...ints.slice(0, index), ...ints.slice(index + 1)]);
1868
2343
  }
1869
- registerInterceptor(method, url, alias) {
2344
+ registerInterceptor(method, url, alias, fixtureFile) {
1870
2345
  if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
1871
- const command = `cy.intercept('${method}', '${this.urlToWildcard(url, method)}').as('${alias}')`;
2346
+ const wildcard = this.urlToWildcard(url, method);
2347
+ const command = fixtureFile ? `cy.intercept('${method}', '${wildcard}', { fixture: '${fixtureFile}' }).as('${alias}')` : `cy.intercept('${method}', '${wildcard}').as('${alias}')`;
1872
2348
  const current = this.interceptors$.getValue();
1873
2349
  if (!current.includes(command)) {
1874
2350
  this.interceptors$.next([...current, command]);
1875
2351
  }
1876
2352
  }
2353
+ /** Registers a captured response as a Cypress fixture (name → JSON content). */
2354
+ registerFixture(name, content) {
2355
+ if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
2356
+ this.fixtures.set(name, content);
2357
+ }
2358
+ getFixturesSnapshot() {
2359
+ return [...this.fixtures.entries()].map(([name, content]) => ({ name, content }));
2360
+ }
1877
2361
  clearCommands() {
1878
2362
  this.commands$.next([]);
1879
2363
  this.interceptors$.next([]);
2364
+ this.fixtures.clear();
1880
2365
  }
1881
2366
  clearInterceptors() {
1882
2367
  this.interceptors$.next([]);
@@ -1930,6 +2415,39 @@ var RecordingService = class {
1930
2415
  this.inputDebounceTimers.clear();
1931
2416
  }
1932
2417
  // ── DOM listeners ─────────────────────────────────────────────────────────
2418
+ /**
2419
+ * Alt+click captures an ASSERTION for the element instead of a click, and
2420
+ * suppresses the element's real action. Runs in the capture phase so it fires
2421
+ * before the app's handlers and before the bubble-phase click listener below
2422
+ * (stopImmediatePropagation prevents a duplicate `.click()`). See spec 009.
2423
+ */
2424
+ listenToAssertClicks() {
2425
+ document.addEventListener(
2426
+ "click",
2427
+ (e) => {
2428
+ if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
2429
+ if (!e.altKey) return;
2430
+ const target = e.target;
2431
+ if (!target || this.isOwnElement(target)) return;
2432
+ const tag = target.tagName?.toLowerCase();
2433
+ if (tag === "body" || tag === "html") return;
2434
+ const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2435
+ if (!container) return;
2436
+ const selector = this.getReliableSelector(container);
2437
+ if (!selector || this.isOwnSelector(selector)) return;
2438
+ e.preventDefault();
2439
+ e.stopImmediatePropagation();
2440
+ const emit = () => this.addCommand(inferAssertionCommand(container, selector));
2441
+ const type = (container.getAttribute("type") ?? "").toLowerCase();
2442
+ if (container.tagName.toLowerCase() === "input" && (type === "checkbox" || type === "radio")) {
2443
+ queueMicrotask(emit);
2444
+ } else {
2445
+ emit();
2446
+ }
2447
+ },
2448
+ { capture: true, signal: this.abort.signal }
2449
+ );
2450
+ }
1933
2451
  listenToClicks() {
1934
2452
  document.addEventListener(
1935
2453
  "click",
@@ -1944,6 +2462,70 @@ var RecordingService = class {
1944
2462
  { signal: this.abort.signal }
1945
2463
  );
1946
2464
  }
2465
+ /** Centralised selector resolution for the pointer/key listeners (spec 010). */
2466
+ resolveSelectorFor(target) {
2467
+ if (!target || this.isOwnElement(target)) return null;
2468
+ const tag = target.tagName?.toLowerCase();
2469
+ if (tag === "body" || tag === "html") return null;
2470
+ const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2471
+ if (!container) return null;
2472
+ const selector = this.getReliableSelector(container);
2473
+ if (!selector || this.isOwnSelector(selector)) return null;
2474
+ return selector;
2475
+ }
2476
+ listenToDoubleClicks() {
2477
+ document.addEventListener(
2478
+ "dblclick",
2479
+ (e) => {
2480
+ if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
2481
+ if (e.altKey) return;
2482
+ const selector = this.resolveSelectorFor(e.target);
2483
+ if (!selector) return;
2484
+ const clickCmd = `cy.get('${selector}').click()`;
2485
+ let cmds = this.commands$.getValue();
2486
+ let removed = 0;
2487
+ while (removed < 2 && cmds.length > 0 && cmds[cmds.length - 1] === clickCmd) {
2488
+ cmds = cmds.slice(0, -1);
2489
+ removed++;
2490
+ }
2491
+ if (removed > 0) this.commands$.next(cmds);
2492
+ this.addCommand(`cy.get('${selector}').dblclick()`);
2493
+ },
2494
+ { signal: this.abort.signal }
2495
+ );
2496
+ }
2497
+ listenToContextMenu() {
2498
+ document.addEventListener(
2499
+ "contextmenu",
2500
+ (e) => {
2501
+ if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
2502
+ const selector = this.resolveSelectorFor(e.target);
2503
+ if (!selector) return;
2504
+ this.addCommand(`cy.get('${selector}').rightclick()`);
2505
+ },
2506
+ { signal: this.abort.signal }
2507
+ );
2508
+ }
2509
+ /** Enter → type('{enter}'), Escape → type('{esc}'), only inside a field. */
2510
+ listenToKeys() {
2511
+ document.addEventListener(
2512
+ "keydown",
2513
+ (e) => {
2514
+ if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
2515
+ const key = e.key;
2516
+ if (key !== "Enter" && key !== "Escape") return;
2517
+ const target = e.target;
2518
+ const tag = target?.tagName?.toLowerCase();
2519
+ if (tag !== "input" && tag !== "textarea" && tag !== "select") return;
2520
+ const selector = this.resolveSelectorFor(target);
2521
+ if (!selector) return;
2522
+ this.flushInputDebounce(target);
2523
+ const token = key === "Enter" ? "{enter}" : "{esc}";
2524
+ this.addCommand(`cy.get('${selector}').type('${token}')`);
2525
+ },
2526
+ { signal: this.abort.signal }
2527
+ );
2528
+ }
1947
2529
  listenToInput() {
1948
2530
  document.addEventListener(
1949
2531
  "input",
@@ -2022,11 +2604,32 @@ var RecordingService = class {
2022
2604
  }
2023
2605
  return;
2024
2606
  }
2607
+ if (!this.isInteractiveElement(target)) {
2608
+ const interactive = target.closest(
2609
+ 'button,a,input,select,textarea,label,summary,mat-option,[role="button"],[role="link"],[role="menuitem"],[role="option"],[role="tab"]'
2610
+ );
2611
+ if (!interactive) return;
2612
+ target = interactive;
2613
+ }
2025
2614
  }
2026
2615
  const tag = target.tagName.toLowerCase();
2027
2616
  if (tag === "input" || tag === "textarea" || tag === "select") {
2028
2617
  const container2 = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2029
- if (!container2) this.selectorNotFound$.next({ target, action: "click" });
2618
+ if (!container2) {
2619
+ this.selectorNotFound$.next({ target, action: "click" });
2620
+ return;
2621
+ }
2622
+ if (tag === "input") {
2623
+ const input = target;
2624
+ const type = (input.getAttribute("type") ?? "").toLowerCase();
2625
+ if (type === "checkbox" || type === "radio") {
2626
+ const selector2 = this.getReliableSelector(container2);
2627
+ if (selector2 && !this.isOwnSelector(selector2)) {
2628
+ const action = type === "radio" || input.checked ? "check" : "uncheck";
2629
+ this.addCommand(`cy.get('${selector2}').${action}()`);
2630
+ }
2631
+ }
2632
+ }
2030
2633
  return;
2031
2634
  }
2032
2635
  const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
@@ -2040,6 +2643,10 @@ var RecordingService = class {
2040
2643
  this.handleMatOptionClick(target, selector);
2041
2644
  return;
2042
2645
  }
2646
+ if (!selector) {
2647
+ this.selectorNotFound$.next({ target, action: "click" });
2648
+ return;
2649
+ }
2043
2650
  this.addGenericCommand({
2044
2651
  selector,
2045
2652
  action: (s) => `cy.get('${s}').click()`
@@ -2073,16 +2680,30 @@ var RecordingService = class {
2073
2680
  this.inputDebounceTimers.set(
2074
2681
  target,
2075
2682
  setTimeout(() => {
2076
- const selector = this.getReliableSelector(container);
2077
- const value = target.value.replace(/'/g, "\\'");
2078
- this.addGenericCommand({
2079
- selector,
2080
- action: (s) => `cy.get('${s}').clear().type('${value}')`
2081
- });
2082
2683
  this.inputDebounceTimers.delete(target);
2684
+ this.recordInputValue(target);
2083
2685
  }, 1e3)
2084
2686
  );
2085
2687
  }
2688
+ /** Records a text field's current value as a `clear().type()` command. */
2689
+ recordInputValue(target) {
2690
+ const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2691
+ if (!container) return;
2692
+ const selector = this.getReliableSelector(container);
2693
+ const value = target.value.replace(/'/g, "\\'");
2694
+ this.addGenericCommand({
2695
+ selector,
2696
+ action: (s) => `cy.get('${s}').clear().type('${value}')`
2697
+ });
2698
+ }
2699
+ /** Flushes any pending debounced value for an element, recording it now. */
2700
+ flushInputDebounce(target) {
2701
+ const timer = this.inputDebounceTimers.get(target);
2702
+ if (timer === void 0) return;
2703
+ clearTimeout(timer);
2704
+ this.inputDebounceTimers.delete(target);
2705
+ this.recordInputValue(target);
2706
+ }
2086
2707
  // ── Select helpers ────────────────────────────────────────────────────────
2087
2708
  handleSelectEvent(target) {
2088
2709
  const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
@@ -2177,9 +2798,9 @@ var PersistenceService = class {
2177
2798
  return this._db;
2178
2799
  }
2179
2800
  // ── Tests ─────────────────────────────────────────────────────────────────
2180
- async insertTest(name, commands = [], interceptors = [], tags = [], notes) {
2801
+ async insertTest(name, commands = [], interceptors = [], tags = [], notes, ticketId) {
2181
2802
  const db = await this.getDB();
2182
- const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {}, ...notes ? { notes } : {} };
2803
+ const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {}, ...notes ? { notes } : {}, ...ticketId ? { ticketId } : {} };
2183
2804
  const id = await db.add("tests", record);
2184
2805
  if (commands.length) await this.insertCommands(commands, id);
2185
2806
  if (interceptors.length) await this.insertInterceptors(interceptors, id);
@@ -2333,6 +2954,31 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2333
2954
  await this.setConfigKey("cypressDirectoryHandle", dirHandle);
2334
2955
  await this.setConfigKey("allowReadWriteFiles", "true");
2335
2956
  }
2957
+ /**
2958
+ * Writes captured fixtures into `cypress/fixtures/` using the configured Cypress
2959
+ * folder handle (spec 012). Returns the number written. Throws if no folder is
2960
+ * configured or write permission is denied.
2961
+ */
2962
+ async writeFixtures(fixtures) {
2963
+ if (!fixtures.length) return 0;
2964
+ const config = await this.getGeneralConfig();
2965
+ const dirHandle = config?.["cypressDirectoryHandle"];
2966
+ if (!dirHandle) throw new Error("No Cypress folder configured");
2967
+ const perm = dirHandle;
2968
+ let state = await perm.queryPermission({ mode: "readwrite" });
2969
+ if (state !== "granted") state = await perm.requestPermission({ mode: "readwrite" });
2970
+ if (state !== "granted") throw new Error("Write permission denied");
2971
+ const fixturesDir = await dirHandle.getDirectoryHandle("fixtures", { create: true });
2972
+ let written = 0;
2973
+ for (const fx of fixtures) {
2974
+ const fileHandle = await fixturesDir.getFileHandle(fx.name, { create: true });
2975
+ const writable = await fileHandle.createWritable();
2976
+ await writable.write(fx.content);
2977
+ await writable.close();
2978
+ written++;
2979
+ }
2980
+ return written;
2981
+ }
2336
2982
  async bulkInsertWithoutId(store, items) {
2337
2983
  if (!Array.isArray(items)) return;
2338
2984
  const db = await this.getDB();
@@ -2385,6 +3031,14 @@ function parseJsonObject(text) {
2385
3031
  }
2386
3032
  return null;
2387
3033
  }
3034
+ function prettyJsonOrNull(text) {
3035
+ try {
3036
+ const parsed = JSON.parse(text);
3037
+ if (parsed && typeof parsed === "object") return JSON.stringify(parsed, null, 2);
3038
+ } catch {
3039
+ }
3040
+ return null;
3041
+ }
2388
3042
  function parseRequestBody(init) {
2389
3043
  const body = init?.body;
2390
3044
  if (typeof body === "string") return parseJsonObject(body);
@@ -2418,6 +3072,9 @@ var HttpMonitor = class {
2418
3072
  isExtendedHttpEnabled() {
2419
3073
  return localStorage.getItem("extendedHttpCommands") === "true";
2420
3074
  }
3075
+ isFixtureModeEnabled() {
3076
+ return localStorage.getItem("fixtureMode") === "true";
3077
+ }
2421
3078
  installFetch() {
2422
3079
  if (this.originalFetch) return;
2423
3080
  const originalFetch = window.fetch;
@@ -2481,14 +3138,21 @@ var HttpMonitor = class {
2481
3138
  const alias = generateAlias(method, url);
2482
3139
  this.recording.registerInterceptor(method, url, alias);
2483
3140
  const extendedHttp = this.isExtendedHttpEnabled();
3141
+ const fixtureMode = this.isFixtureModeEnabled();
2484
3142
  const requestBody = extendedHttp ? parseRequestBody(init) : null;
3143
+ let responseText = null;
2485
3144
  let responseBody = null;
2486
- if (extendedHttp) {
3145
+ if (extendedHttp || fixtureMode && method === "GET") {
2487
3146
  try {
2488
- responseBody = parseJsonObject(await responseClone.text());
3147
+ responseText = await responseClone.text();
3148
+ if (extendedHttp) responseBody = parseJsonObject(responseText);
2489
3149
  } catch {
2490
3150
  }
2491
3151
  }
3152
+ if (fixtureMode && method === "GET" && responseText !== null) {
3153
+ const pretty = prettyJsonOrNull(responseText);
3154
+ if (pretty !== null) this.recording.registerFixture(`${alias}.json`, pretty);
3155
+ }
2492
3156
  const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
2493
3157
  this.recording.addCommand(cmd);
2494
3158
  }
@@ -2496,6 +3160,10 @@ var HttpMonitor = class {
2496
3160
  if (!INTERCEPTED_METHODS.includes(method)) return;
2497
3161
  const alias = generateAlias(method, url);
2498
3162
  this.recording.registerInterceptor(method, url, alias);
3163
+ if (this.isFixtureModeEnabled() && method === "GET") {
3164
+ const pretty = prettyJsonOrNull(responseText);
3165
+ if (pretty !== null) this.recording.registerFixture(`${alias}.json`, pretty);
3166
+ }
2499
3167
  const extendedHttp = this.isExtendedHttpEnabled();
2500
3168
  const responseBody = extendedHttp ? parseJsonObject(responseText) : null;
2501
3169
  const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
@@ -2613,6 +3281,10 @@ var LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES = `
2613
3281
  align-items: stretch;
2614
3282
  box-sizing: border-box;
2615
3283
  overflow: hidden !important;
3284
+ /* Left-align modal body text/code (SweetAlert defaults it to centre, which
3285
+ inherits into the panels \u2014 including their shadow DOM). Titles/buttons keep
3286
+ their own alignment. */
3287
+ text-align: left !important;
2616
3288
  }
2617
3289
  /* The single wrapper div we inject inside each modal fills the container */
2618
3290
  .swal2-html-container > div {
@@ -3021,12 +3693,39 @@ if (!customElements.get("test-previsualizer")) {
3021
3693
  customElements.define("test-previsualizer", TestPrevisualizerElement);
3022
3694
  }
3023
3695
 
3696
+ // src/models/issue-tracker.model.ts
3697
+ var ISSUE_TRACKER_PRESETS = {
3698
+ jira: { id: "jira", label: "Jira", urlPattern: "{baseUrl}/browse/{id}", idPattern: /^[A-Z][A-Z0-9]+-\d+$/ },
3699
+ github: { id: "github", label: "GitHub Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
3700
+ gitlab: { id: "gitlab", label: "GitLab Issues", urlPattern: "{baseUrl}/-/issues/{id}", idPattern: /^\d+$/ },
3701
+ azure: { id: "azure", label: "Azure DevOps", urlPattern: "{baseUrl}/_workitems/edit/{id}", idPattern: /^\d+$/ },
3702
+ bitbucket: { id: "bitbucket", label: "Bitbucket Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
3703
+ custom: { id: "custom", label: "Custom", urlPattern: "{baseUrl}/{id}", idPattern: /.+/ }
3704
+ };
3705
+ var DEFAULT_ISSUE_TRACKER_CONFIG = {
3706
+ enabled: false,
3707
+ provider: "jira",
3708
+ baseUrl: ""
3709
+ };
3710
+
3711
+ // src/utils/ticket.utils.ts
3712
+ function buildTicketUrl(ticketId, config) {
3713
+ if (!config.enabled || !ticketId.trim() || !config.baseUrl.trim()) return null;
3714
+ const preset = ISSUE_TRACKER_PRESETS[config.provider];
3715
+ const base = config.baseUrl.replace(/\/+$/, "");
3716
+ return preset.urlPattern.replace("{baseUrl}", base).replace("{id}", encodeURIComponent(ticketId.trim()));
3717
+ }
3718
+ function isValidTicketId(ticketId, provider) {
3719
+ if (!ticketId.trim()) return false;
3720
+ return ISSUE_TRACKER_PRESETS[provider].idPattern.test(ticketId.trim());
3721
+ }
3722
+
3024
3723
  // src/components/save-test/save-test.styles.ts
3025
3724
  var SAVE_TEST_STYLES = `
3026
3725
  :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
3027
3726
  * { box-sizing: border-box; }
3028
3727
  .container { padding: 24px 28px; text-align: left; }
3029
- .btn-row, p { text-align: center; }
3728
+ .btn-row { text-align: center; }
3030
3729
  p { margin: 0 0 20px; font-size: 14px; color: #8b949e; line-height: 1.5; }
3031
3730
  .btn-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 16px; }
3032
3731
  button {
@@ -3096,10 +3795,15 @@ function renderSaveTestAsk(t) {
3096
3795
  </div>
3097
3796
  </div>`;
3098
3797
  }
3099
- function renderSaveTestDesc(description, notes, tags, t) {
3798
+ function renderSaveTestDesc(description, notes, tags, ticketId, issueTrackerEnabled, ticketWarn, t) {
3100
3799
  const chipsHtml = tags.map(
3101
3800
  (tag) => `<span class="chip">${escHtml(tag)}<button class="chip-del" data-tag="${escAttr(tag)}" title="${t("SAVE_TEST.REMOVE_TAG_TITLE")}">\u2715</button></span>`
3102
3801
  ).join("");
3802
+ const ticketHtml = issueTrackerEnabled ? `
3803
+ <span class="tag-label">${t("SAVE_TEST.TICKET_LABEL")}</span>
3804
+ <input id="ticket-input" type="text" placeholder="${escAttr(t("SAVE_TEST.TICKET_PLACEHOLDER"))}"
3805
+ value="${escAttr(ticketId)}" autocomplete="off" />
3806
+ ${ticketWarn ? `<span class="ticket-warn">\u26A0 ${t("SAVE_TEST.TICKET_WARN")}</span>` : ""}` : "";
3103
3807
  return `
3104
3808
  <div class="container">
3105
3809
  <p>${t("SAVE_TEST.DESC_LABEL")} (<code>it()</code>):</p>
@@ -3113,6 +3817,7 @@ function renderSaveTestDesc(description, notes, tags, t) {
3113
3817
  <button class="btn-tag-add" id="btn-add-tag">${t("SAVE_TEST.ADD_TAG")}</button>
3114
3818
  </div>
3115
3819
  <div class="chips" id="chips-container">${chipsHtml || `<span style="color:#484f58;font-size:11px">${t("SAVE_TEST.NO_TAGS")}</span>`}</div>
3820
+ ${ticketHtml}
3116
3821
  <div class="btn-row">
3117
3822
  <button class="btn-primary" id="btn-confirm">${t("SAVE_TEST.SAVE_BTN")}</button>
3118
3823
  <button class="btn-success" id="btn-export">${t("SAVE_TEST.SAVE_AND_EDIT")}</button>
@@ -3128,6 +3833,8 @@ var SaveTestElement = class extends HTMLElement {
3128
3833
  description = "";
3129
3834
  notes = "";
3130
3835
  tags = [];
3836
+ ticketId = "";
3837
+ issueTrackerConfig = DEFAULT_ISSUE_TRACKER_CONFIG;
3131
3838
  translation = translationService;
3132
3839
  constructor() {
3133
3840
  super();
@@ -3144,19 +3851,20 @@ var SaveTestElement = class extends HTMLElement {
3144
3851
  this.render();
3145
3852
  }
3146
3853
  confirmSave() {
3147
- this.dispatch("savetest", { description: this.description.trim(), notes: this.notes, tags: [...this.tags] });
3854
+ this.dispatch("savetest", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() });
3148
3855
  }
3149
3856
  confirmSaveAndExport() {
3150
- this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags] });
3857
+ this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() });
3151
3858
  }
3152
3859
  cancel() {
3153
- this.dispatch("savetest", { description: null, notes: "", tags: [] });
3860
+ this.dispatch("savetest", { description: null, notes: "", tags: [], ticketId: "" });
3154
3861
  }
3155
3862
  restartComponent() {
3156
3863
  this._step = "ask";
3157
3864
  this.description = "";
3158
3865
  this.notes = "";
3159
3866
  this.tags = [];
3867
+ this.ticketId = "";
3160
3868
  this.render();
3161
3869
  }
3162
3870
  addTag(tag) {
@@ -3173,6 +3881,10 @@ var SaveTestElement = class extends HTMLElement {
3173
3881
  t(key) {
3174
3882
  return this.translation.translate(key);
3175
3883
  }
3884
+ get ticketIdWarning() {
3885
+ if (!this.ticketId.trim() || !this.issueTrackerConfig.enabled) return false;
3886
+ return !isValidTicketId(this.ticketId, this.issueTrackerConfig.provider);
3887
+ }
3176
3888
  dispatch(type, detail) {
3177
3889
  this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
3178
3890
  }
@@ -3182,16 +3894,21 @@ var SaveTestElement = class extends HTMLElement {
3182
3894
  this.shadow.getElementById("btn-yes")?.addEventListener("click", () => this.askSave());
3183
3895
  this.shadow.getElementById("btn-no")?.addEventListener("click", () => this.cancel());
3184
3896
  } else {
3185
- this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.notes, this.tags, this.t.bind(this))}`;
3897
+ this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.notes, this.tags, this.ticketId, this.issueTrackerConfig.enabled, this.ticketIdWarning, this.t.bind(this))}`;
3186
3898
  const descInput = this.shadow.getElementById("desc-input");
3187
3899
  const notesInput = this.shadow.getElementById("notes-input");
3188
3900
  const tagInput = this.shadow.getElementById("tag-input");
3901
+ const ticketInput = this.shadow.getElementById("ticket-input");
3189
3902
  descInput.addEventListener("input", () => {
3190
3903
  this.description = descInput.value;
3191
3904
  });
3192
3905
  notesInput.addEventListener("input", () => {
3193
3906
  this.notes = notesInput.value;
3194
3907
  });
3908
+ ticketInput?.addEventListener("input", () => {
3909
+ this.ticketId = ticketInput.value;
3910
+ this.render();
3911
+ });
3195
3912
  const tryAddTag = () => {
3196
3913
  this.addTag(tagInput.value);
3197
3914
  tagInput.value = "";
@@ -3283,6 +4000,18 @@ var TEST_EDITOR_STYLES = `
3283
4000
  padding: 1px 7px; border-radius: 20px; font-size: 10px;
3284
4001
  background: rgba(47,129,247,0.1); color: #8b949e; border: 1px solid rgba(47,129,247,0.2);
3285
4002
  }
4003
+ .ticket-badge, .ticket-link {
4004
+ padding: 1px 7px; border-radius: 20px; font-size: 10px; font-weight: 500;
4005
+ background: rgba(210,153,34,0.12); color: #d29922; border: 1px solid rgba(210,153,34,0.3);
4006
+ white-space: nowrap; flex-shrink: 0;
4007
+ }
4008
+ .ticket-link {
4009
+ text-decoration: none; cursor: pointer;
4010
+ transition: background 0.12s, border-color 0.12s;
4011
+ }
4012
+ .ticket-link::before { content: '\u{1F3AB} '; font-size: 9px; }
4013
+ .ticket-link:hover { background: rgba(210,153,34,0.22); border-color: rgba(210,153,34,0.55); color: #e3b341; }
4014
+ .ticket-badge::before { content: '\u{1F3AB} '; font-size: 9px; }
3286
4015
  .btn-icon {
3287
4016
  padding: 3px 8px; border: none; border-radius: 6px; cursor: pointer;
3288
4017
  font-size: 11px; background: #21262d; color: #8b949e;
@@ -3314,6 +4043,13 @@ var TEST_EDITOR_STYLES = `
3314
4043
  .code-preview::-webkit-scrollbar { height: 4px; }
3315
4044
  .code-preview::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
3316
4045
  .code-preview-icp { margin-top: 8px; }
4046
+ .ticket-group { margin-bottom: 8px; }
4047
+ .ticket-group-header {
4048
+ padding: 4px 10px; margin-bottom: 4px;
4049
+ font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.7px;
4050
+ color: #d29922; border-left: 3px solid rgba(210,153,34,0.5);
4051
+ background: rgba(210,153,34,0.06); border-radius: 0 4px 4px 0;
4052
+ }
3317
4053
  .sh-kw { color: #ff7b72; }
3318
4054
  .sh-bi { color: #d2a8ff; }
3319
4055
  .sh-str { color: #a5d6ff; }
@@ -3420,8 +4156,48 @@ function highlightLine(line) {
3420
4156
  }
3421
4157
 
3422
4158
  // src/components/test-editor/test-editor.template.ts
4159
+ function renderTestRow(test, i, state, t) {
4160
+ const { selectMode, selectedIds, expandedIndex, interceptorsByTest, locale, issueTrackerConfig } = state;
4161
+ const expanded = expandedIndex === i;
4162
+ const date = new Date(test.createdAt).toLocaleString(locale, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
4163
+ const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
4164
+ const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
4165
+ const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
4166
+ const hasIcps = (test.interceptors ?? []).length > 0;
4167
+ const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
4168
+ ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
4169
+ });`;
4170
+ const icpBlockCode = icps.length ? `beforeEach(() => {
4171
+ // Auto-generated Cypress interceptors
4172
+ ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
4173
+ });` : "";
4174
+ const notesHtml = expanded && test.notes ? `<p class="test-notes">${escHtml(test.notes)}</p>` : "";
4175
+ const ticketId = test.ticketId;
4176
+ const ticketUrl = ticketId ? buildTicketUrl(ticketId, issueTrackerConfig) : null;
4177
+ 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>` : "";
4178
+ const body = expanded ? `
4179
+ <div class="row-body">
4180
+ ${notesHtml}
4181
+ <pre class="code-preview">${syntaxHighlight(itBlockCode)}</pre>
4182
+ ${icpBlockCode ? `<pre class="code-preview code-preview-icp">${syntaxHighlight(icpBlockCode)}</pre>` : ""}
4183
+ </div>` : "";
4184
+ return `
4185
+ <div class="row${selectedIds.has(test.id) ? " selected-row" : ""}">
4186
+ <div class="row-header" data-action="expand" data-idx="${i}">
4187
+ ${checkbox}
4188
+ <span class="test-name">${escHtml(test.name)}</span>
4189
+ ${tagsHtml}
4190
+ ${ticketHtml}
4191
+ <span class="test-date">${date}</span>
4192
+ <button class="btn-icon" data-action="copy-cmds" data-idx="${i}" title="${t("TEST_EDITOR.COPY_CMDS_BTN")}">${t("TEST_EDITOR.COPY_CMDS_BTN")}</button>
4193
+ ${hasIcps ? `<button class="btn-icon" data-action="copy-icps" data-idx="${i}" title="${t("TEST_EDITOR.COPY_ICPS_BTN")}">${t("TEST_EDITOR.COPY_ICPS_BTN")}</button>` : ""}
4194
+ <button class="btn-icon btn-del" data-action="delete" data-id="${test.id}" title="${t("TEST_EDITOR.DELETE_TITLE")}">\u{1F5D1}</button>
4195
+ </div>
4196
+ ${body}
4197
+ </div>`;
4198
+ }
3423
4199
  function renderTestEditor(state, t) {
3424
- const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest, locale } = state;
4200
+ const { tags, visible, selectedVisible, activeTag, selectMode, describeName, groupByTicket } = state;
3425
4201
  const tagFilterHtml = tags.length ? `<div class="tag-filter">
3426
4202
  ${tags.map((tag) => `<button class="tag-chip${activeTag === tag ? " active" : ""}" data-filter-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}
3427
4203
  </div>` : `<div class="tag-filter" style="color:#484f58;font-size:11px">${t("TEST_EDITOR.NO_TAGS")}</div>`;
@@ -3430,44 +4206,36 @@ function renderTestEditor(state, t) {
3430
4206
  <input id="describe-name" type="text" placeholder="${t("TEST_EDITOR.DESCRIBE_PLACEHOLDER")}" value="${escAttr(describeName)}" />
3431
4207
  <button class="btn-gen-describe" id="btn-gen-describe">${t("TEST_EDITOR.COPY_DESCRIBE")}</button>
3432
4208
  </div>` : "";
3433
- const rows = visible.map((test, i) => {
3434
- const expanded = expandedIndex === i;
3435
- const date = new Date(test.createdAt).toLocaleString(locale, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
3436
- const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
3437
- const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
3438
- const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
3439
- const hasIcps = (test.interceptors ?? []).length > 0;
3440
- const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
3441
- ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
3442
- });`;
3443
- const icpBlockCode = icps.length ? `beforeEach(() => {
3444
- // Auto-generated Cypress interceptors
3445
- ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
3446
- });` : "";
3447
- const notesHtml = expanded && test.notes ? `<p class="test-notes">${escHtml(test.notes)}</p>` : "";
3448
- const body = expanded ? `
3449
- <div class="row-body">
3450
- ${notesHtml}
3451
- <pre class="code-preview">${syntaxHighlight(itBlockCode)}</pre>
3452
- ${icpBlockCode ? `<pre class="code-preview code-preview-icp">${syntaxHighlight(icpBlockCode)}</pre>` : ""}
3453
- </div>` : "";
3454
- return `
3455
- <div class="row${selectedIds.has(test.id) ? " selected-row" : ""}">
3456
- <div class="row-header" data-action="expand" data-idx="${i}">
3457
- ${checkbox}
3458
- <span class="test-name">${escHtml(test.name)}</span>
3459
- ${tagsHtml}
3460
- <span class="test-date">${date}</span>
3461
- <button class="btn-icon" data-action="copy-cmds" data-idx="${i}" title="${t("TEST_EDITOR.COPY_CMDS_BTN")}">${t("TEST_EDITOR.COPY_CMDS_BTN")}</button>
3462
- ${hasIcps ? `<button class="btn-icon" data-action="copy-icps" data-idx="${i}" title="${t("TEST_EDITOR.COPY_ICPS_BTN")}">${t("TEST_EDITOR.COPY_ICPS_BTN")}</button>` : ""}
3463
- <button class="btn-icon btn-del" data-action="delete" data-id="${test.id}" title="${t("TEST_EDITOR.DELETE_TITLE")}">\u{1F5D1}</button>
3464
- </div>
3465
- ${body}
3466
- </div>`;
3467
- }).join("");
4209
+ let rows;
4210
+ if (groupByTicket) {
4211
+ const groups = /* @__PURE__ */ new Map();
4212
+ for (const test of visible) {
4213
+ const key = test.ticketId?.trim() || "";
4214
+ const group = groups.get(key) ?? [];
4215
+ group.push(test);
4216
+ groups.set(key, group);
4217
+ }
4218
+ const sortedKeys = [...groups.keys()].sort(
4219
+ (a, b) => a === "" ? 1 : b === "" ? -1 : a.localeCompare(b)
4220
+ );
4221
+ rows = sortedKeys.map((key) => {
4222
+ const group = groups.get(key);
4223
+ const header = `<div class="ticket-group-header">${key ? escHtml(key) : t("TEST_EDITOR.NO_TICKET")}</div>`;
4224
+ const groupRows = group.map((test) => {
4225
+ const i = visible.indexOf(test);
4226
+ return renderTestRow(test, i, state, t);
4227
+ }).join("");
4228
+ return `<div class="ticket-group">${header}${groupRows}</div>`;
4229
+ }).join("");
4230
+ } else {
4231
+ rows = visible.map((test, i) => renderTestRow(test, i, state, t)).join("");
4232
+ }
3468
4233
  return `
3469
4234
  <div class="toolbar">
3470
4235
  ${tagFilterHtml}
4236
+ <button class="btn-select${groupByTicket ? " active" : ""}" id="btn-group-ticket">
4237
+ ${groupByTicket ? t("TEST_EDITOR.UNGROUP_TICKET") : t("TEST_EDITOR.GROUP_BY_TICKET")}
4238
+ </button>
3471
4239
  <button class="btn-select${selectMode ? " active" : ""}" id="btn-select-mode">
3472
4240
  ${selectMode ? t("TEST_EDITOR.CANCEL_SELECT") : t("TEST_EDITOR.MULTI_SELECT")}
3473
4241
  </button>
@@ -3490,6 +4258,8 @@ var TestEditorElement = class extends HTMLElement {
3490
4258
  selectMode = false;
3491
4259
  selectedIds = /* @__PURE__ */ new Set();
3492
4260
  describeName = "";
4261
+ groupByTicket = false;
4262
+ issueTrackerConfig = { ...DEFAULT_ISSUE_TRACKER_CONFIG };
3493
4263
  constructor() {
3494
4264
  super();
3495
4265
  this.shadow = this.attachShadow({ mode: "open" });
@@ -3520,6 +4290,10 @@ var TestEditorElement = class extends HTMLElement {
3520
4290
  hasInterceptors(testId) {
3521
4291
  return Array.isArray(this.interceptorsByTest[testId]) && this.interceptorsByTest[testId].length > 0;
3522
4292
  }
4293
+ toggleGroupByTicket() {
4294
+ this.groupByTicket = !this.groupByTicket;
4295
+ this.render();
4296
+ }
3523
4297
  toggleSelectMode() {
3524
4298
  this.selectMode = !this.selectMode;
3525
4299
  if (!this.selectMode) this.selectedIds.clear();
@@ -3584,8 +4358,11 @@ ${beforeEach}${itBlocks}
3584
4358
  describeName: this.describeName,
3585
4359
  expandedIndex: this.expandedIndex,
3586
4360
  interceptorsByTest: this.interceptorsByTest,
3587
- locale: localeForLang(this.translation.getLang())
4361
+ locale: localeForLang(this.translation.getLang()),
4362
+ groupByTicket: this.groupByTicket,
4363
+ issueTrackerConfig: this.issueTrackerConfig
3588
4364
  }, this.t.bind(this))}`;
4365
+ this.shadow.getElementById("btn-group-ticket")?.addEventListener("click", () => this.toggleGroupByTicket());
3589
4366
  this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
3590
4367
  this.shadow.querySelectorAll("[data-filter-tag]").forEach((el) => {
3591
4368
  el.addEventListener("click", () => {
@@ -3650,7 +4427,7 @@ if (!customElements.get("test-editor")) {
3650
4427
 
3651
4428
  // src/components/configuration/configuration.styles.ts
3652
4429
  var CONFIGURATION_STYLES = `
3653
- :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
4430
+ :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; max-height: 70vh; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #30363d transparent; }
3654
4431
  * { box-sizing: border-box; }
3655
4432
 
3656
4433
  /* \u2500\u2500 Grid container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
@@ -3874,7 +4651,7 @@ function renderExportOverlay(state, t) {
3874
4651
  </div>`;
3875
4652
  }
3876
4653
  function renderConfiguration(state, t) {
3877
- const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes } = state;
4654
+ const { selectedLanguage, advancedHttpConfig, fixtureMode, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes, issueTrackerConfig } = state;
3878
4655
  const langOptions = LANGS.map(
3879
4656
  (l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
3880
4657
  ).join("");
@@ -3902,6 +4679,18 @@ function renderConfiguration(state, t) {
3902
4679
  </label>
3903
4680
  </div>
3904
4681
 
4682
+ <!-- HTTP Fixtures -->
4683
+ <div class="card">
4684
+ <div class="card-hd">${t("CONFIG.FIXTURE_SECTION")}</div>
4685
+ <label class="check-row">
4686
+ <input type="checkbox" id="fixture-toggle" ${fixtureMode ? "checked" : ""} />
4687
+ <div>
4688
+ <div class="check-title">${t("CONFIG.FIXTURE_TITLE")}</div>
4689
+ <div class="check-sub">${t("CONFIG.FIXTURE_SUB")}</div>
4690
+ </div>
4691
+ </label>
4692
+ </div>
4693
+
3905
4694
  <!-- Smart Selector -->
3906
4695
  <div class="card">
3907
4696
  <div class="card-hd">${t("CONFIG.SMART_SELECTOR_SECTION")}</div>
@@ -3983,6 +4772,34 @@ function renderConfiguration(state, t) {
3983
4772
  </div>
3984
4773
  </div>
3985
4774
 
4775
+ <!-- Issue Tracker -->
4776
+ <div class="card card-wide">
4777
+ <div class="card-hd">${t("CONFIG.ISSUE_TRACKER_SECTION")}</div>
4778
+ <label class="check-row">
4779
+ <input type="checkbox" id="issue-tracker-toggle" ${issueTrackerConfig.enabled ? "checked" : ""} />
4780
+ <div>
4781
+ <div class="check-title">${t("CONFIG.ISSUE_TRACKER_TITLE")}</div>
4782
+ <div class="check-sub">${t("CONFIG.ISSUE_TRACKER_SUB")}</div>
4783
+ </div>
4784
+ </label>
4785
+ ${issueTrackerConfig.enabled ? `
4786
+ <div class="field-row" style="margin-top:10px">
4787
+ <span class="field-label">${t("CONFIG.ISSUE_TRACKER_PROVIDER")}</span>
4788
+ <select id="issue-tracker-provider">
4789
+ ${Object.values(ISSUE_TRACKER_PRESETS).map(
4790
+ (p) => `<option value="${p.id}" ${issueTrackerConfig.provider === p.id ? "selected" : ""}>${escHtml(p.label)}</option>`
4791
+ ).join("")}
4792
+ </select>
4793
+ </div>
4794
+ <div class="field-row" style="margin-top:8px">
4795
+ <span class="field-label">${t("CONFIG.ISSUE_TRACKER_BASE_URL")}</span>
4796
+ <input id="issue-tracker-base-url" type="text"
4797
+ placeholder="${escAttr(t("CONFIG.ISSUE_TRACKER_BASE_URL_PH"))}"
4798
+ value="${escAttr(issueTrackerConfig.baseUrl)}"
4799
+ style="flex:1;padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;color:#c9d1d9;font-size:12px;outline:none" />
4800
+ </div>` : ""}
4801
+ </div>
4802
+
3986
4803
  <!-- Data -->
3987
4804
  <div class="card card-wide">
3988
4805
  <div class="card-hd">${t("CONFIG.DATA_SECTION")}</div>
@@ -4006,6 +4823,7 @@ var ConfigurationElement = class extends HTMLElement {
4006
4823
  translation;
4007
4824
  selectedLanguage = "es";
4008
4825
  advancedHttpConfig = false;
4826
+ fixtureMode = false;
4009
4827
  selectorStrategy = "data-cy";
4010
4828
  smartSelectorEnabled = true;
4011
4829
  startHidden = false;
@@ -4015,12 +4833,14 @@ var ConfigurationElement = class extends HTMLElement {
4015
4833
  exportTests = [];
4016
4834
  exportSelectedIds = /* @__PURE__ */ new Set();
4017
4835
  exportSelectedTags = /* @__PURE__ */ new Set();
4836
+ issueTrackerConfig = { ...DEFAULT_ISSUE_TRACKER_CONFIG };
4018
4837
  filesystemGranted = false;
4019
4838
  cypressFolderName = null;
4020
4839
  constructor() {
4021
4840
  super();
4022
4841
  this.shadow = this.attachShadow({ mode: "open" });
4023
4842
  this.advancedHttpConfig = localStorage.getItem("extendedHttpCommands") === "true";
4843
+ this.fixtureMode = localStorage.getItem("fixtureMode") === "true";
4024
4844
  }
4025
4845
  connectedCallback() {
4026
4846
  if (!this.persistence) this.persistence = new PersistenceService();
@@ -4038,6 +4858,7 @@ var ConfigurationElement = class extends HTMLElement {
4038
4858
  this.translation.setLang(this.selectedLanguage);
4039
4859
  }
4040
4860
  this.advancedHttpConfig = localStorage.getItem("extendedHttpCommands") === "true";
4861
+ this.fixtureMode = localStorage.getItem("fixtureMode") === "true";
4041
4862
  this.selectorStrategy = config?.["selectorStrategy"] ?? "data-cy";
4042
4863
  this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
4043
4864
  this.startHidden = config?.["startHidden"] === "true";
@@ -4049,6 +4870,11 @@ var ConfigurationElement = class extends HTMLElement {
4049
4870
  this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
4050
4871
  const handle = config?.["cypressDirectoryHandle"];
4051
4872
  this.cypressFolderName = handle?.name ?? null;
4873
+ this.issueTrackerConfig = {
4874
+ enabled: config?.["issueTrackerEnabled"] === "true",
4875
+ provider: config?.["issueTrackerProvider"] ?? DEFAULT_ISSUE_TRACKER_CONFIG.provider,
4876
+ baseUrl: config?.["issueTrackerBaseUrl"] ?? ""
4877
+ };
4052
4878
  this.render();
4053
4879
  }
4054
4880
  async onLanguageChange(lang) {
@@ -4063,6 +4889,12 @@ var ConfigurationElement = class extends HTMLElement {
4063
4889
  this.persistence.setConfig({ extendedHttpCommands: checked ? "true" : "false" });
4064
4890
  this.render();
4065
4891
  }
4892
+ onFixtureModeChange(checked) {
4893
+ this.fixtureMode = checked;
4894
+ localStorage.setItem("fixtureMode", checked ? "true" : "false");
4895
+ this.persistence.setConfig({ fixtureMode: checked ? "true" : "false" });
4896
+ this.render();
4897
+ }
4066
4898
  async onStartHiddenChange(checked) {
4067
4899
  this.startHidden = checked;
4068
4900
  await this.persistence.setConfig({ startHidden: checked ? "true" : "false" });
@@ -4091,6 +4923,23 @@ var ConfigurationElement = class extends HTMLElement {
4091
4923
  this.dispatchEvent(new CustomEvent("selectorstrategychange", { detail: strategy, bubbles: true, composed: true }));
4092
4924
  this.render();
4093
4925
  }
4926
+ async onIssueTrackerEnabledChange(enabled) {
4927
+ this.issueTrackerConfig = { ...this.issueTrackerConfig, enabled };
4928
+ await this.persistence.setConfig({ issueTrackerEnabled: enabled ? "true" : "false" });
4929
+ this.dispatchEvent(new CustomEvent("issuetrackerchange", { detail: { ...this.issueTrackerConfig }, bubbles: true, composed: true }));
4930
+ this.render();
4931
+ }
4932
+ async onIssueTrackerProviderChange(provider) {
4933
+ this.issueTrackerConfig = { ...this.issueTrackerConfig, provider };
4934
+ await this.persistence.setConfig({ issueTrackerProvider: provider });
4935
+ this.dispatchEvent(new CustomEvent("issuetrackerchange", { detail: { ...this.issueTrackerConfig }, bubbles: true, composed: true }));
4936
+ this.render();
4937
+ }
4938
+ async onIssueTrackerBaseUrlChange(baseUrl) {
4939
+ this.issueTrackerConfig = { ...this.issueTrackerConfig, baseUrl };
4940
+ await this.persistence.setConfig({ issueTrackerBaseUrl: baseUrl });
4941
+ this.dispatchEvent(new CustomEvent("issuetrackerchange", { detail: { ...this.issueTrackerConfig }, bubbles: true, composed: true }));
4942
+ }
4094
4943
  async changeFolder() {
4095
4944
  try {
4096
4945
  await this.persistence.requestDirectoryPermissions();
@@ -4181,6 +5030,7 @@ var ConfigurationElement = class extends HTMLElement {
4181
5030
  this.shadow.innerHTML = `<style>${CONFIGURATION_STYLES}</style>${renderConfiguration({
4182
5031
  selectedLanguage: this.selectedLanguage,
4183
5032
  advancedHttpConfig: this.advancedHttpConfig,
5033
+ fixtureMode: this.fixtureMode,
4184
5034
  selectorStrategy: this.selectorStrategy,
4185
5035
  filesystemGranted: this.filesystemGranted,
4186
5036
  cypressFolderName: this.cypressFolderName,
@@ -4191,7 +5041,8 @@ var ConfigurationElement = class extends HTMLElement {
4191
5041
  exportMode: this.exportMode,
4192
5042
  exportTests: this.exportTests,
4193
5043
  exportSelectedIds: this.exportSelectedIds,
4194
- exportSelectedTags: this.exportSelectedTags
5044
+ exportSelectedTags: this.exportSelectedTags,
5045
+ issueTrackerConfig: this.issueTrackerConfig
4195
5046
  }, this.t.bind(this))}`;
4196
5047
  ;
4197
5048
  this.shadow.getElementById("lang-select").addEventListener(
@@ -4202,6 +5053,10 @@ var ConfigurationElement = class extends HTMLElement {
4202
5053
  "change",
4203
5054
  (e) => this.onAdvancedHttpConfigChange(e.target.checked)
4204
5055
  );
5056
+ this.shadow.getElementById("fixture-toggle").addEventListener(
5057
+ "change",
5058
+ (e) => this.onFixtureModeChange(e.target.checked)
5059
+ );
4205
5060
  this.shadow.getElementById("smart-selector-toggle").addEventListener(
4206
5061
  "change",
4207
5062
  (e) => this.onSmartSelectorChange(e.target.checked)
@@ -4219,6 +5074,19 @@ var ConfigurationElement = class extends HTMLElement {
4219
5074
  "change",
4220
5075
  (e) => this.onSelectorStrategyChange(e.target.value)
4221
5076
  );
5077
+ ;
5078
+ this.shadow.getElementById("issue-tracker-toggle")?.addEventListener(
5079
+ "change",
5080
+ (e) => this.onIssueTrackerEnabledChange(e.target.checked)
5081
+ );
5082
+ this.shadow.getElementById("issue-tracker-provider")?.addEventListener(
5083
+ "change",
5084
+ (e) => this.onIssueTrackerProviderChange(e.target.value)
5085
+ );
5086
+ this.shadow.getElementById("issue-tracker-base-url")?.addEventListener(
5087
+ "change",
5088
+ (e) => this.onIssueTrackerBaseUrlChange(e.target.value)
5089
+ );
4222
5090
  this.shadow.getElementById("btn-change-folder")?.addEventListener("click", () => this.changeFolder());
4223
5091
  this.shadow.getElementById("btn-revoke")?.addEventListener("click", () => this.revokeAccess());
4224
5092
  this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.openExportDialog());
@@ -5175,6 +6043,155 @@ if (!customElements.get("file-preview")) {
5175
6043
  customElements.define("file-preview", FilePreviewElement);
5176
6044
  }
5177
6045
 
6046
+ // src/components/help-panel/help-panel.styles.ts
6047
+ var HELP_PANEL_STYLES = `
6048
+ :host { display: block; }
6049
+ *, *::before, *::after { box-sizing: border-box; }
6050
+
6051
+ .help-tabs {
6052
+ display: flex;
6053
+ gap: 4px;
6054
+ border-bottom: 1px solid #21262d;
6055
+ margin-bottom: 8px;
6056
+ }
6057
+ .help-tab {
6058
+ appearance: none;
6059
+ border: none;
6060
+ background: transparent;
6061
+ color: #8b949e;
6062
+ font-size: 12px;
6063
+ font-weight: 600;
6064
+ padding: 8px 12px;
6065
+ cursor: pointer;
6066
+ border-bottom: 2px solid transparent;
6067
+ margin-bottom: -1px;
6068
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
6069
+ transition: color .12s, border-color .12s;
6070
+ }
6071
+ .help-tab:hover { color: #c9d1d9; }
6072
+ .help-tab.active { color: #e6edf3; border-bottom-color: #2f81f7; }
6073
+
6074
+ .help-panel {
6075
+ max-height: 60vh;
6076
+ overflow-y: auto;
6077
+ padding: 4px 6px 8px;
6078
+ color: #c9d1d9;
6079
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
6080
+ font-size: 13px;
6081
+ line-height: 1.55;
6082
+ }
6083
+
6084
+ .help-intro {
6085
+ margin: 0 0 12px;
6086
+ color: #8b949e;
6087
+ font-size: 12px;
6088
+ }
6089
+
6090
+ .help-sec { margin: 0 0 14px; }
6091
+
6092
+ .help-sec-hd {
6093
+ margin: 0 0 6px;
6094
+ font-size: 12px;
6095
+ font-weight: 700;
6096
+ color: #e6edf3;
6097
+ letter-spacing: .3px;
6098
+ border-bottom: 1px solid #21262d;
6099
+ padding-bottom: 4px;
6100
+ }
6101
+
6102
+ .help-list {
6103
+ margin: 0;
6104
+ padding: 0;
6105
+ list-style: none;
6106
+ }
6107
+
6108
+ .help-list li {
6109
+ padding: 3px 0 3px 14px;
6110
+ position: relative;
6111
+ color: #c9d1d9;
6112
+ }
6113
+
6114
+ .help-list li::before {
6115
+ content: '\xB7';
6116
+ position: absolute;
6117
+ left: 3px;
6118
+ color: #2f81f7;
6119
+ font-weight: 700;
6120
+ }
6121
+ `;
6122
+
6123
+ // src/components/help-panel/help-panel.template.ts
6124
+ init_html_utils();
6125
+ var REFERENCE_SECTIONS = [
6126
+ { title: "HELP.SEC_RECORDING", items: ["HELP.RECORDING_1", "HELP.RECORDING_2"] },
6127
+ {
6128
+ title: "HELP.SEC_SHORTCUTS",
6129
+ items: ["HELP.SC_RECORD", "HELP.SC_PAUSE", "HELP.SC_TESTS", "HELP.SC_COMMANDS", "HELP.SC_CONFIG", "HELP.SC_VISIBILITY", "HELP.SC_HELP"]
6130
+ },
6131
+ {
6132
+ title: "HELP.SEC_CAPTURED",
6133
+ items: ["HELP.CAP_CLICK", "HELP.CAP_DBLCLICK", "HELP.CAP_RIGHTCLICK", "HELP.CAP_TYPE", "HELP.CAP_CHECK", "HELP.CAP_SELECT", "HELP.CAP_KEYS", "HELP.CAP_ROUTE", "HELP.CAP_HTTP"]
6134
+ },
6135
+ { title: "HELP.SEC_ASSERT", items: ["HELP.ASSERT_1", "HELP.ASSERT_2"] },
6136
+ { title: "HELP.SEC_PANELS", items: ["HELP.PANEL_TESTS", "HELP.PANEL_COMMANDS", "HELP.PANEL_CONFIG", "HELP.PANEL_FILES"] },
6137
+ { title: "HELP.SEC_SELECTOR", items: ["HELP.SELECTOR_1", "HELP.SELECTOR_2"] },
6138
+ { title: "HELP.SEC_DATA", items: ["HELP.DATA_1", "HELP.DATA_2"] },
6139
+ { title: "HELP.SEC_ADVANCED", items: ["HELP.ADV_INVISIBLE", "HELP.ADV_RUNNER", "HELP.ADV_CROSSAPP", "HELP.ADV_DRAG"] }
6140
+ ];
6141
+ var GUIDE_SECTIONS = [
6142
+ { 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"] },
6143
+ { 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"] },
6144
+ { 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"] }
6145
+ ];
6146
+ function sectionsHtml(sections, t) {
6147
+ return sections.map(
6148
+ (s) => `
6149
+ <section class="help-sec">
6150
+ <h3 class="help-sec-hd">${escHtml(t(s.title))}</h3>
6151
+ <ul class="help-list">${s.items.map((k) => `<li>${escHtml(t(k))}</li>`).join("")}</ul>
6152
+ </section>`
6153
+ ).join("");
6154
+ }
6155
+ function renderHelpPanel(t, activeTab = "reference") {
6156
+ const tab = (id, key) => `<button class="help-tab ${activeTab === id ? "active" : ""}" data-tab="${id}">${escHtml(t(key))}</button>`;
6157
+ const body = activeTab === "guide" ? `<p class="help-intro">${escHtml(t("HELP.GUIDE_INTRO"))}</p>${sectionsHtml(GUIDE_SECTIONS, t)}` : `<p class="help-intro">${escHtml(t("HELP.INTRO"))}</p>${sectionsHtml(REFERENCE_SECTIONS, t)}`;
6158
+ return `
6159
+ <div class="help-root">
6160
+ <div class="help-tabs" role="tablist">
6161
+ ${tab("reference", "HELP.TAB_REFERENCE")}
6162
+ ${tab("guide", "HELP.TAB_GUIDE")}
6163
+ </div>
6164
+ <div class="help-panel" data-tab-panel="${activeTab}">${body}</div>
6165
+ </div>`;
6166
+ }
6167
+
6168
+ // src/components/help-panel/help-panel.ts
6169
+ var HelpPanelElement = class extends HTMLElement {
6170
+ shadow;
6171
+ activeTab = "reference";
6172
+ translation;
6173
+ constructor() {
6174
+ super();
6175
+ this.shadow = this.attachShadow({ mode: "open" });
6176
+ }
6177
+ connectedCallback() {
6178
+ if (!this.translation) this.translation = new TranslationService();
6179
+ this.render();
6180
+ }
6181
+ render() {
6182
+ this.shadow.innerHTML = `<style>${HELP_PANEL_STYLES}</style>${renderHelpPanel(this.translation.translate.bind(this.translation), this.activeTab)}`;
6183
+ this.shadow.querySelectorAll("[data-tab]").forEach((btn) => {
6184
+ btn.addEventListener("click", () => {
6185
+ this.activeTab = btn.dataset["tab"] ?? "reference";
6186
+ this.render();
6187
+ });
6188
+ });
6189
+ }
6190
+ };
6191
+ if (!customElements.get("help-panel")) {
6192
+ customElements.define("help-panel", HelpPanelElement);
6193
+ }
6194
+
5178
6195
  // src/components/lib-e2e-recorder/lib-e2e-recorder.ts
5179
6196
  var import_sweetalert2 = __toESM(require("sweetalert2"), 1);
5180
6197
 
@@ -5182,22 +6199,15 @@ var import_sweetalert2 = __toESM(require("sweetalert2"), 1);
5182
6199
  var DIRECTIONS = ["up-left", "up-right", "down-left", "down-right"];
5183
6200
  function directionBlocks() {
5184
6201
  return DIRECTIONS.map((dir) => {
5185
- const sx = dir.endsWith("left") ? -1 : 1;
5186
- const sy = dir.startsWith("up") ? -1 : 1;
5187
6202
  const tv = dir.startsWith("up") ? "bottom" : "top";
5188
6203
  const th = dir.endsWith("left") ? "right" : "left";
5189
6204
  const ov = tv === "bottom" ? "top" : "bottom";
5190
6205
  const oh = th === "right" ? "left" : "right";
5191
6206
  const sel = `.widget[data-expand="${dir}"]`;
5192
- const labelGeneral = `${sel} .btn-action::after { ${oh}: calc(100% + 9px); ${th}: auto; top: 50%; bottom: auto; transform: translateY(-50%); }`;
5193
- const labelEnds = `${sel} .btn-action[data-n="1"]::after, ${sel} .btn-action[data-n="4"]::after { ${ov}: calc(100% + 9px); ${tv}: auto; left: 50%; right: auto; transform: translateX(-50%); }`;
5194
6207
  return `
5195
- ${sel} { --sx: ${sx}; --sy: ${sy}; }
5196
- ${sel} .btn-toggle { ${tv}: 24px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
5197
- ${sel} .btn-pause { ${tv}: 78px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
5198
- ${sel} .btn-action { ${tv}: 28px; ${th}: 28px; ${ov}: auto; ${oh}: auto; }
5199
- ${labelGeneral}
5200
- ${labelEnds}
6208
+ ${sel} .btn-toggle { ${tv}: 24px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
6209
+ ${sel} .btn-pause { ${tv}: 78px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
6210
+ ${sel} .action-menu { ${tv}: 118px; ${th}: 8px; ${ov}: auto; ${oh}: auto; transform-origin: ${tv} ${th}; }
5201
6211
  `;
5202
6212
  }).join("\n");
5203
6213
  }
@@ -5207,9 +6217,10 @@ function getRecorderStyles(rec, paused) {
5207
6217
  *, *::before, *::after { box-sizing: border-box; }
5208
6218
 
5209
6219
  /*
5210
- * Invisible 190\xD7190 hit area. Position (left/top) is set from JS so the
5211
- * widget can be dragged; --sx/--sy + data-expand orient the radial menu so
5212
- * it always expands toward the viewport interior. Defaults to bottom-right.
6220
+ * Hit area. Position (left/top) is set from JS so the widget can be dragged;
6221
+ * data-expand orients the action-menu popover toward the viewport interior.
6222
+ * The menu is a DOM child, so hovering it keeps :host(.widget):hover alive even
6223
+ * though it visually overflows this box. Defaults to bottom-right.
5213
6224
  */
5214
6225
  .widget {
5215
6226
  position: fixed;
@@ -5218,12 +6229,10 @@ function getRecorderStyles(rec, paused) {
5218
6229
  width: 190px;
5219
6230
  height: 190px;
5220
6231
  z-index: 2147483647;
5221
- --sx: -1;
5222
- --sy: -1;
5223
6232
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
5224
6233
  }
5225
6234
 
5226
- /* \u2500\u2500 Toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
6235
+ /* \u2500\u2500 Toggle (FAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5227
6236
  .btn-toggle {
5228
6237
  position: absolute;
5229
6238
  bottom: 24px;
@@ -5276,83 +6285,58 @@ function getRecorderStyles(rec, paused) {
5276
6285
  .btn-pause:hover { transform: scale(1.12); }
5277
6286
  .btn-pause:active { transform: scale(0.93); }
5278
6287
 
5279
- /* \u2500\u2500 Action buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5280
- .btn-action {
6288
+ /* \u2500\u2500 Action menu (labelled grid popover) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
6289
+ .action-menu {
5281
6290
  position: absolute;
5282
- bottom: 28px;
5283
- right: 28px;
5284
- width: 36px;
5285
- height: 36px;
5286
- border-radius: 50%;
5287
- border: none;
5288
- cursor: pointer;
5289
- font-size: 16px;
5290
- display: flex;
5291
- align-items: center;
5292
- justify-content: center;
5293
- background: rgba(13,17,23,.92);
6291
+ display: grid;
6292
+ grid-template-columns: repeat(3, 1fr);
6293
+ gap: 4px;
6294
+ padding: 8px;
6295
+ width: 174px;
6296
+ background: rgba(13,17,23,.97);
6297
+ border: 1px solid rgba(48,54,61,.9);
6298
+ border-radius: 12px;
6299
+ box-shadow: 0 8px 28px rgba(0,0,0,.5);
5294
6300
  backdrop-filter: blur(14px);
5295
6301
  -webkit-backdrop-filter: blur(14px);
5296
- color: #8b949e;
5297
- box-shadow: 0 4px 18px rgba(0,0,0,.45), 0 0 0 1px rgba(48,54,61,.75);
5298
6302
  opacity: 0;
5299
- transform: scale(0.35);
6303
+ transform: scale(0.9);
5300
6304
  pointer-events: none;
5301
- transition: opacity .15s, transform .18s ease-in,
5302
- background .15s, color .12s, box-shadow .15s;
5303
- }
5304
-
5305
- /* Label chip */
5306
- .btn-action::after {
5307
- content: attr(data-label);
5308
- position: absolute;
5309
- right: calc(100% + 9px);
5310
- top: 50%;
5311
- transform: translateY(-50%);
5312
- background: rgba(13,17,23,.95);
5313
- color: #e6edf3;
5314
- font-size: 11px;
5315
- font-weight: 500;
5316
- padding: 3px 9px;
5317
- border-radius: 6px;
5318
- white-space: nowrap;
5319
- pointer-events: none;
5320
- opacity: 0;
5321
- transition: opacity .15s .05s;
5322
- border: 1px solid rgba(48,54,61,.8);
5323
- box-shadow: 0 2px 8px rgba(0,0,0,.35);
6305
+ transition: opacity .16s, transform .2s cubic-bezier(.34,1.56,.64,1);
6306
+ z-index: 3;
5324
6307
  }
5325
- .btn-action:hover::after { opacity: 1; }
5326
- .btn-action:hover { background: #21262d; color: #e6edf3; }
5327
- .btn-action:active { background: #30363d !important; }
5328
-
5329
- /* Expand: spring + stagger */
5330
- .widget:hover .btn-action {
6308
+ .widget:hover .action-menu {
5331
6309
  opacity: 1;
6310
+ transform: scale(1);
5332
6311
  pointer-events: all;
5333
- transition: opacity .2s, transform .32s cubic-bezier(.34,1.56,.64,1),
5334
- background .15s, color .12s, box-shadow .15s;
5335
6312
  }
5336
6313
 
5337
- /* Arc positions \u2014 signs come from --sx/--sy (data-expand) */
5338
- .widget:hover .btn-action[data-n="1"] { /* vertical extreme */
5339
- transform: translateY(calc(var(--sy) * 90px)) scale(1);
5340
- transition-delay: .03s;
5341
- }
5342
- .widget:hover .btn-action[data-n="2"] { /* diagonal near-vertical */
5343
- transform: translate(calc(var(--sx) * 45px), calc(var(--sy) * 78px)) scale(1);
5344
- transition-delay: .07s;
5345
- }
5346
- .widget:hover .btn-action[data-n="3"] { /* diagonal near-horizontal */
5347
- transform: translate(calc(var(--sx) * 78px), calc(var(--sy) * 45px)) scale(1);
5348
- transition-delay: .11s;
6314
+ .action-item {
6315
+ display: flex;
6316
+ flex-direction: column;
6317
+ align-items: center;
6318
+ justify-content: flex-start;
6319
+ gap: 3px;
6320
+ padding: 8px 4px;
6321
+ border: none;
6322
+ border-radius: 8px;
6323
+ cursor: pointer;
6324
+ background: transparent;
6325
+ color: #c9d1d9;
6326
+ font-size: 18px;
6327
+ line-height: 1;
6328
+ transition: background .12s;
5349
6329
  }
5350
- .widget:hover .btn-action[data-n="4"] { /* horizontal extreme */
5351
- transform: translateX(calc(var(--sx) * 90px)) scale(1);
5352
- transition-delay: .15s;
6330
+ .action-item:hover { background: #21262d; }
6331
+ .action-item .label {
6332
+ font-size: 9px;
6333
+ font-weight: 500;
6334
+ color: #8b949e;
6335
+ white-space: nowrap;
5353
6336
  }
6337
+ .action-item:hover .label { color: #e6edf3; }
5354
6338
 
5355
- /* Per-direction anchors + label sides */
6339
+ /* Per-direction anchors */
5356
6340
  ${directionBlocks()}
5357
6341
 
5358
6342
  /* \u2500\u2500 REC / PAUSED badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
@@ -5386,14 +6370,18 @@ function getRecorderStyles(rec, paused) {
5386
6370
  function renderRecorderWidget(rec, paused, t) {
5387
6371
  return `
5388
6372
  <div class="widget">
5389
- <button class="btn-action" data-n="1" data-action="config"
5390
- data-label="${t("RECORDER.BTN_CONFIG")}">\u2699\uFE0F</button>
5391
- <button class="btn-action" data-n="2" data-action="browse"
5392
- data-label="${t("RECORDER.BTN_FILES")}">\u{1F4C1}</button>
5393
- <button class="btn-action" data-n="3" data-action="commands"
5394
- data-label="${t("RECORDER.BTN_COMMANDS")}">\u2328\uFE0F</button>
5395
- <button class="btn-action" data-n="4" data-action="tests"
5396
- data-label="${t("RECORDER.BTN_TESTS")}">\u{1F4CB}</button>
6373
+ <div class="action-menu">
6374
+ <button class="action-item" data-action="config">
6375
+ <span class="ico">\u2699\uFE0F</span><span class="label">${t("RECORDER.BTN_CONFIG")}</span></button>
6376
+ <button class="action-item" data-action="browse">
6377
+ <span class="ico">\u{1F4C1}</span><span class="label">${t("RECORDER.BTN_FILES")}</span></button>
6378
+ <button class="action-item" data-action="commands">
6379
+ <span class="ico">\u2328\uFE0F</span><span class="label">${t("RECORDER.BTN_COMMANDS")}</span></button>
6380
+ <button class="action-item" data-action="tests">
6381
+ <span class="ico">\u{1F4CB}</span><span class="label">${t("RECORDER.BTN_TESTS")}</span></button>
6382
+ <button class="action-item" data-action="help">
6383
+ <span class="ico">\u2753</span><span class="label">${t("RECORDER.BTN_HELP")}</span></button>
6384
+ </div>
5397
6385
  <button class="btn-pause" data-action="pause"
5398
6386
  title="${paused ? t("RECORDER.RESUME_TITLE") : t("RECORDER.PAUSE_TITLE")}">
5399
6387
  ${paused ? "\u25B6" : "\u23F8"}
@@ -5406,6 +6394,26 @@ function renderRecorderWidget(rec, paused, t) {
5406
6394
  <div class="rec-badge">${paused ? t("RECORDER.BADGE_PAUSED") : t("RECORDER.BADGE_REC")}</div>`;
5407
6395
  }
5408
6396
 
6397
+ // src/utils/fixture-convert.utils.ts
6398
+ function toFixtureInterceptors(interceptors, fixtures) {
6399
+ const fixtureNames = new Set(fixtures.map((f) => f.name));
6400
+ return interceptors.map((icp) => {
6401
+ const match = icp.match(/\.as\('([^']+)'\)$/);
6402
+ if (!match) return icp;
6403
+ const fixtureFile = `${match[1]}.json`;
6404
+ if (!fixtureNames.has(fixtureFile)) return icp;
6405
+ return icp.replace(/\)\.as\(/, `, { fixture: '${fixtureFile}' }).as(`);
6406
+ });
6407
+ }
6408
+ function simplifyFixtureWaits(commands, fixtures) {
6409
+ const fixtureAliases = new Set(fixtures.map((f) => f.name.replace(/\.json$/, "")));
6410
+ return commands.map((cmd) => {
6411
+ const match = cmd.match(/^cy\.wait\('@([^']+)'\)/);
6412
+ if (!match || !fixtureAliases.has(match[1])) return cmd;
6413
+ return `cy.wait('@${match[1]}').then((interception) => { })`;
6414
+ });
6415
+ }
6416
+
5409
6417
  // src/components/lib-e2e-recorder/lib-e2e-recorder.ts
5410
6418
  var LibE2eRecorderElement = class extends HTMLElement {
5411
6419
  shadow;
@@ -5432,6 +6440,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
5432
6440
  httpMonitor;
5433
6441
  smartSelectorEnabled = true;
5434
6442
  _needsRecordingRebuild = false;
6443
+ issueTrackerConfig = { ...DEFAULT_ISSUE_TRACKER_CONFIG };
5435
6444
  recording;
5436
6445
  persistence;
5437
6446
  translation;
@@ -5445,6 +6454,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
5445
6454
  isSaveTestDialogOpen = false;
5446
6455
  isSettingsDialogOpen = false;
5447
6456
  isAdvancedEditorDialogOpen = false;
6457
+ isHelpDialogOpen = false;
5448
6458
  constructor() {
5449
6459
  super();
5450
6460
  this.shadow = this.attachShadow({ mode: "open" });
@@ -5511,6 +6521,8 @@ var LibE2eRecorderElement = class extends HTMLElement {
5511
6521
  } else {
5512
6522
  localStorage.setItem("extendedHttpCommands", config["extendedHttpCommands"] ?? "true");
5513
6523
  }
6524
+ const fx = await this.persistence.getConfig("fixtureMode");
6525
+ localStorage.setItem("fixtureMode", fx?.["fixtureMode"] ?? "false");
5514
6526
  }
5515
6527
  async initLanguage() {
5516
6528
  const config = await this.persistence.getConfig("language");
@@ -5572,6 +6584,11 @@ var LibE2eRecorderElement = class extends HTMLElement {
5572
6584
  const strategy = config?.["selectorStrategy"];
5573
6585
  if (strategy) this.recording.selectorStrategy = strategy;
5574
6586
  this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
6587
+ this.issueTrackerConfig = {
6588
+ enabled: config?.["issueTrackerEnabled"] === "true",
6589
+ provider: config?.["issueTrackerProvider"] ?? DEFAULT_ISSUE_TRACKER_CONFIG.provider,
6590
+ baseUrl: config?.["issueTrackerBaseUrl"] ?? ""
6591
+ };
5575
6592
  }
5576
6593
  // ── cross-app session continuity (spec 006) ───────────────────────────────
5577
6594
  /**
@@ -5778,6 +6795,9 @@ var LibE2eRecorderElement = class extends HTMLElement {
5778
6795
  } else if (key === "3") {
5779
6796
  event.preventDefault();
5780
6797
  this.showSettingsDialog();
6798
+ } else if (key === "h" && event.shiftKey) {
6799
+ event.preventDefault();
6800
+ this.showHelpDialog();
5781
6801
  }
5782
6802
  }
5783
6803
  toggleVisibility() {
@@ -5994,6 +7014,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
5994
7014
  const child = document.createElement("test-editor");
5995
7015
  child.persistence = this.persistence;
5996
7016
  child.translation = this.translation;
7017
+ child.issueTrackerConfig = this.issueTrackerConfig;
5997
7018
  container.appendChild(child);
5998
7019
  },
5999
7020
  willClose: () => {
@@ -6019,15 +7040,16 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6019
7040
  if (!container) return;
6020
7041
  const child = document.createElement("save-test");
6021
7042
  child.translation = this.translation;
7043
+ child.issueTrackerConfig = this.issueTrackerConfig;
6022
7044
  container.appendChild(child);
6023
7045
  child.addEventListener("savetest", (e) => {
6024
- const { description, notes, tags } = e.detail ?? {};
6025
- this.onSaveTest(description ?? null, tags ?? [], notes ?? "");
7046
+ const { description, notes, tags, ticketId } = e.detail ?? {};
7047
+ this.onSaveTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? "");
6026
7048
  import_sweetalert2.default.close();
6027
7049
  });
6028
7050
  child.addEventListener("saveandexport", (e) => {
6029
- const { description, notes, tags } = e.detail ?? {};
6030
- this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "");
7051
+ const { description, notes, tags, ticketId } = e.detail ?? {};
7052
+ this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? "");
6031
7053
  import_sweetalert2.default.close();
6032
7054
  });
6033
7055
  },
@@ -6063,6 +7085,9 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6063
7085
  this.style.display = this.isVisible ? "" : "none";
6064
7086
  });
6065
7087
  child.addEventListener("resetwidgetposition", () => this.resetWidgetPosition());
7088
+ child.addEventListener("issuetrackerchange", (e) => {
7089
+ this.issueTrackerConfig = e.detail;
7090
+ });
6066
7091
  },
6067
7092
  willClose: () => {
6068
7093
  this.isSettingsDialogOpen = false;
@@ -6071,6 +7096,31 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6071
7096
  this.resizePopup();
6072
7097
  });
6073
7098
  }
7099
+ showHelpDialog() {
7100
+ this.toggleModal("isHelpDialogOpen", () => {
7101
+ import_sweetalert2.default.fire({
7102
+ title: this.translation.translate("HELP.TITLE"),
7103
+ html: '<div id="help-modal-content" style="padding:0"></div>',
7104
+ showCloseButton: true,
7105
+ showConfirmButton: false,
7106
+ width: 560,
7107
+ color: "#e6edf3",
7108
+ didOpen: () => {
7109
+ makeSwalDraggable();
7110
+ setSwal2DataCyAttribute();
7111
+ const container = document.getElementById("help-modal-content");
7112
+ if (!container) return;
7113
+ const child = document.createElement("help-panel");
7114
+ child.translation = this.translation;
7115
+ container.appendChild(child);
7116
+ },
7117
+ willClose: () => {
7118
+ this.isHelpDialogOpen = false;
7119
+ }
7120
+ });
7121
+ this.resizePopup();
7122
+ });
7123
+ }
6074
7124
  showAdvancedEditorDialog(testId) {
6075
7125
  this.toggleModal("isAdvancedEditorDialogOpen", () => {
6076
7126
  import_sweetalert2.default.fire({
@@ -6135,7 +7185,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6135
7185
  showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "", notes = "") {
6136
7186
  import_sweetalert2.default.fire({
6137
7187
  title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
6138
- html: '<div id="file-editor-modal-content" style="padding:0;height:540px"></div>',
7188
+ html: '<div id="file-editor-modal-content" style="flex:1;min-height:0;display:flex;flex-direction:column;padding:0"></div>',
6139
7189
  showCloseButton: false,
6140
7190
  showConfirmButton: false,
6141
7191
  allowOutsideClick: false,
@@ -6144,6 +7194,20 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6144
7194
  didOpen: () => {
6145
7195
  makeSwalDraggable();
6146
7196
  setSwal2DataCyAttribute();
7197
+ const popup = import_sweetalert2.default.getPopup();
7198
+ if (popup) {
7199
+ popup.style.height = "640px";
7200
+ const htmlContainer = popup.querySelector(".swal2-html-container");
7201
+ if (htmlContainer) {
7202
+ htmlContainer.style.flex = "1";
7203
+ htmlContainer.style.minHeight = "0";
7204
+ htmlContainer.style.overflow = "hidden";
7205
+ htmlContainer.style.padding = "0";
7206
+ htmlContainer.style.margin = "0";
7207
+ htmlContainer.style.display = "flex";
7208
+ htmlContainer.style.flexDirection = "column";
7209
+ }
7210
+ }
6147
7211
  const container = document.getElementById("file-editor-modal-content");
6148
7212
  if (!container) return;
6149
7213
  const child = document.createElement("file-preview");
@@ -6176,24 +7240,36 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6176
7240
  this.resizePopup();
6177
7241
  }
6178
7242
  // ── save handlers ─────────────────────────────────────────────────────────
6179
- async onSaveTest(description, tags = [], notes = "") {
6180
- if (description) {
6181
- await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes);
6182
- this.recording.clearCommands();
6183
- this.clearRecordingHistory();
6184
- this.cypressCommands = [];
6185
- this.interceptors = [];
6186
- }
6187
- }
6188
- async onSaveAndExportTest(description, tags = [], notes = "") {
6189
- if (description) {
6190
- const id = await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes);
6191
- this.recording.clearCommands();
6192
- this.clearRecordingHistory();
6193
- this.cypressCommands = [];
6194
- this.interceptors = [];
6195
- setTimeout(() => this.showAdvancedEditorDialog(id), 300);
7243
+ async onSaveTest(description, tags = [], notes = "", ticketId = "") {
7244
+ if (!description) return;
7245
+ await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes, ticketId || void 0);
7246
+ this.recording.clearCommands();
7247
+ this.clearRecordingHistory();
7248
+ this.cypressCommands = [];
7249
+ this.interceptors = [];
7250
+ }
7251
+ async onSaveAndExportTest(description, tags = [], notes = "", ticketId = "") {
7252
+ if (!description) return;
7253
+ const fixtures = this.recording.getFixturesSnapshot();
7254
+ const isFixtureMode = this.httpMonitor?.isFixtureModeEnabled() ?? false;
7255
+ let finalInterceptors = this.interceptors;
7256
+ let finalCommands = this.cypressCommands;
7257
+ if (isFixtureMode && fixtures.length > 0) {
7258
+ try {
7259
+ const n = await this.persistence.writeFixtures(fixtures);
7260
+ finalInterceptors = toFixtureInterceptors(this.interceptors, fixtures);
7261
+ finalCommands = simplifyFixtureWaits(this.cypressCommands, fixtures);
7262
+ showToast(`${this.translation.translate("RECORDER.FIXTURES_WRITTEN_TOAST")} (${n})`);
7263
+ } catch {
7264
+ showToast(this.translation.translate("RECORDER.FIXTURES_NO_FOLDER_TOAST"), false);
7265
+ }
6196
7266
  }
7267
+ const id = await this.persistence.insertTest(description, finalCommands, finalInterceptors, tags, notes, ticketId || void 0);
7268
+ this.recording.clearCommands();
7269
+ this.clearRecordingHistory();
7270
+ this.cypressCommands = [];
7271
+ this.interceptors = [];
7272
+ setTimeout(() => this.showAdvancedEditorDialog(id), 300);
6197
7273
  }
6198
7274
  // ── helpers ───────────────────────────────────────────────────────────────
6199
7275
  toggleModal(flag, openFn) {
@@ -6230,6 +7306,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6230
7306
  this.shadow.querySelector('[data-action="commands"]')?.addEventListener("click", () => this.showCommandsDialog());
6231
7307
  this.shadow.querySelector('[data-action="config"]')?.addEventListener("click", () => this.showSettingsDialog());
6232
7308
  this.shadow.querySelector('[data-action="browse"]')?.addEventListener("click", () => this.showAdvancedEditorDialog());
7309
+ this.shadow.querySelector('[data-action="help"]')?.addEventListener("click", () => this.showHelpDialog());
6233
7310
  this.applyWidgetPosition();
6234
7311
  }
6235
7312
  };
@@ -6238,7 +7315,7 @@ if (!customElements.get("lib-e2e-recorder")) {
6238
7315
  }
6239
7316
 
6240
7317
  // src/index.ts
6241
- var VERSION = "0.6.1";
7318
+ var VERSION = "0.9.0";
6242
7319
  // Annotate the CommonJS export names for ESM import in node:
6243
7320
  0 && (module.exports = {
6244
7321
  ACTIVE_SESSION_BREADCRUMB_KEY,
@@ -6250,6 +7327,7 @@ var VERSION = "0.6.1";
6250
7327
  DEFAULT_RESUME_TTL_MINUTES,
6251
7328
  DRAG_THRESHOLD,
6252
7329
  FilePreviewElement,
7330
+ HelpPanelElement,
6253
7331
  HttpMonitor,
6254
7332
  INPUT_TYPES,
6255
7333
  LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
@@ -6275,6 +7353,7 @@ var VERSION = "0.6.1";
6275
7353
  clampTogglePosition,
6276
7354
  defaultTogglePosition,
6277
7355
  generateAlias,
7356
+ inferAssertionCommand,
6278
7357
  injectStyles,
6279
7358
  isLang,
6280
7359
  isLocalHost,