lib-e2e-cypress-for-dummys-ts 0.9.0 → 0.10.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
@@ -30,6 +30,41 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
+ // src/utils/code-format.utils.ts
34
+ function gcd(a, b) {
35
+ while (b) {
36
+ const t = b;
37
+ b = a % b;
38
+ a = t;
39
+ }
40
+ return a;
41
+ }
42
+ function escapeSingleQuotes(value) {
43
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
44
+ }
45
+ function escapeCssAttrValue(value) {
46
+ return value.replace(/"/g, '\\"');
47
+ }
48
+ function normalizeBlock(code, baseIndent) {
49
+ const lines = code.split("\n").map((l) => l.trimEnd());
50
+ const nonEmpty = lines.filter((l) => l.trim().length > 0);
51
+ if (!nonEmpty.length) return "";
52
+ const indents = nonEmpty.map((l) => l.length - l.trimStart().length);
53
+ const minIndent = Math.min(...indents);
54
+ const relIndents = [...new Set(indents.map((n) => n - minIndent).filter((n) => n > 0))];
55
+ const step = relIndents.length > 0 ? relIndents.reduce(gcd) : 2;
56
+ return lines.map((l) => {
57
+ if (!l.trim()) return "";
58
+ const level = Math.round((l.length - l.trimStart().length - minIndent) / step);
59
+ return baseIndent + " ".repeat(level) + l.trimStart();
60
+ }).join("\n");
61
+ }
62
+ var init_code_format_utils = __esm({
63
+ "src/utils/code-format.utils.ts"() {
64
+ "use strict";
65
+ }
66
+ });
67
+
33
68
  // src/utils/selector-quality.utils.ts
34
69
  function getSelectorQuality(el) {
35
70
  if (el.hasAttribute("data-cy") || el.hasAttribute("data-testid") || el.hasAttribute("aria-label")) {
@@ -46,11 +81,11 @@ function getSelectorQuality(el) {
46
81
  }
47
82
  function buildPickerSelector(el) {
48
83
  const dataCy = el.getAttribute("data-cy");
49
- if (dataCy) return `[data-cy="${dataCy}"]`;
84
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
50
85
  const dataTestid = el.getAttribute("data-testid");
51
- if (dataTestid) return `[data-testid="${dataTestid}"]`;
86
+ if (dataTestid) return `[data-testid="${escapeCssAttrValue(dataTestid)}"]`;
52
87
  const ariaLabel = el.getAttribute("aria-label");
53
- if (ariaLabel) return `[aria-label="${ariaLabel}"]`;
88
+ if (ariaLabel) return `[aria-label="${escapeCssAttrValue(ariaLabel)}"]`;
54
89
  const rawId = el.id;
55
90
  if (rawId && rawId.length < 25 && /^[a-zA-Z][\w-]*$/.test(rawId) && !FORBIDDEN_ID_PREFIXES.some((p) => rawId.startsWith(p)) && !/^\d+$/.test(rawId)) {
56
91
  return `#${rawId}`;
@@ -84,6 +119,7 @@ var FORBIDDEN_ID_PREFIXES;
84
119
  var init_selector_quality_utils = __esm({
85
120
  "src/utils/selector-quality.utils.ts"() {
86
121
  "use strict";
122
+ init_code_format_utils();
87
123
  FORBIDDEN_ID_PREFIXES = [
88
124
  "cdk-",
89
125
  "mat-",
@@ -314,6 +350,7 @@ var init_selector_picker = __esm({
314
350
  "src/components/selector-picker/selector-picker.ts"() {
315
351
  "use strict";
316
352
  init_selector_quality_utils();
353
+ init_code_format_utils();
317
354
  init_selector_picker_styles();
318
355
  init_selector_picker_template();
319
356
  OWN_DATA_CY = "lib-e2e-cypress-for-dummys";
@@ -433,7 +470,7 @@ var init_selector_picker = __esm({
433
470
  confirm(index) {
434
471
  const row = this.rows[index];
435
472
  if (!row) return;
436
- const command = `cy.get('${row.selector}').click()`;
473
+ const command = `cy.get('${escapeSingleQuotes(row.selector)}').click()`;
437
474
  this.recording.appendCommand(command);
438
475
  this.dispatchEvent(new CustomEvent("selectorchosen", { detail: command, bubbles: true, composed: true }));
439
476
  this.closeSilently();
@@ -451,8 +488,8 @@ var init_selector_picker = __esm({
451
488
  this.remove();
452
489
  }
453
490
  };
454
- if (!customElements.get("selector-picker")) {
455
- customElements.define("selector-picker", SelectorPickerElement);
491
+ if (!customElements.get("lib-e2e-selector-picker")) {
492
+ customElements.define("lib-e2e-selector-picker", SelectorPickerElement);
456
493
  }
457
494
  }
458
495
  });
@@ -482,18 +519,19 @@ __export(index_exports, {
482
519
  SUPPORTED_LANGS: () => SUPPORTED_LANGS,
483
520
  SaveTestElement: () => SaveTestElement,
484
521
  SelectorPickerElement: () => SelectorPickerElement,
485
- Subject: () => Subject,
486
522
  TOGGLE_MARGIN: () => TOGGLE_MARGIN,
487
523
  TestEditorElement: () => TestEditorElement,
488
524
  TestPrevisualizerElement: () => TestPrevisualizerElement,
489
525
  TransformationService: () => TransformationService,
490
526
  TranslationService: () => TranslationService,
491
527
  VERSION: () => VERSION,
528
+ _resetHttpMonitorState: () => _resetHttpMonitorState,
492
529
  advancedTestTransformationService: () => advancedTestTransformationService,
493
530
  boxOffsetForDirection: () => boxOffsetForDirection,
494
531
  boxTopLeftFor: () => boxTopLeftFor,
495
532
  clampTogglePosition: () => clampTogglePosition,
496
533
  defaultTogglePosition: () => defaultTogglePosition,
534
+ ensurePopupDimensions: () => ensurePopupDimensions,
497
535
  generateAlias: () => generateAlias,
498
536
  inferAssertionCommand: () => inferAssertionCommand,
499
537
  injectStyles: () => injectStyles,
@@ -503,7 +541,6 @@ __export(index_exports, {
503
541
  makeModalResizable: () => makeModalResizable,
504
542
  makeSwalDraggable: () => makeSwalDraggable,
505
543
  makeSwalDraggableByContentId: () => makeSwalDraggableByContentId,
506
- persistenceService: () => persistenceService,
507
544
  resolveExpandDirection: () => resolveExpandDirection,
508
545
  selectTestsForExport: () => selectTestsForExport,
509
546
  setSwal2DataCyAttribute: () => setSwal2DataCyAttribute,
@@ -650,7 +687,10 @@ var I18N_ES = {
650
687
  NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc.",
651
688
  TICKET_LABEL: "Ticket / Issue (opcional):",
652
689
  TICKET_PLACEHOLDER: "Ej: PROJ-123, #42",
653
- TICKET_WARN: "ID no v\xE1lido para el proveedor configurado (se guardar\xE1 igualmente)"
690
+ TICKET_WARN: "ID no v\xE1lido para el proveedor configurado (se guardar\xE1 igualmente)",
691
+ CONFIRM_DISCARD: "\xBFSeguro que quieres descartar el test grabado? Se perder\xE1n todos los pasos registrados.",
692
+ CONFIRM_DISCARD_BTN: "S\xED, descartar",
693
+ BACK_BTN: "Volver"
654
694
  },
655
695
  TEST_EDITOR: {
656
696
  NO_TAGS: "Sin etiquetas",
@@ -732,7 +772,30 @@ var I18N_ES = {
732
772
  ISSUE_TRACKER_SUB: "A\xF1ade un ID de ticket al guardar y genera un enlace clickable en el listado.",
733
773
  ISSUE_TRACKER_PROVIDER: "Proveedor",
734
774
  ISSUE_TRACKER_BASE_URL: "URL base",
735
- ISSUE_TRACKER_BASE_URL_PH: "Ej: https://miproyecto.atlassian.net"
775
+ ISSUE_TRACKER_BASE_URL_PH: "Ej: https://miproyecto.atlassian.net",
776
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
777
+ LOGIN_SETUP_NOT_CONFIGURED: "No configurado",
778
+ LOGIN_SETUP_SET_UP_BTN: "Configurar",
779
+ LOGIN_SETUP_TITLE: "Configuraci\xF3n de login",
780
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Crear scaffold",
781
+ LOGIN_SETUP_SELECT_EXISTING: "Seleccionar fichero existente",
782
+ LOGIN_SETUP_FILE_NAME_LABEL: "Nombre del fichero",
783
+ LOGIN_SETUP_FILE_NAME_PH: "Ej: cypress/common-services/login.service.ts",
784
+ LOGIN_SETUP_RESCAN: "\u{1F504} Re-escanear",
785
+ LOGIN_SETUP_NO_FUNCTIONS: "No se encontraron funciones exportadas. Rellena el scaffold y re-escanea.",
786
+ LOGIN_SETUP_BEFORE_LABEL: "Funci\xF3n before() (una vez por suite)",
787
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "Funci\xF3n beforeEach() (antes de cada test)",
788
+ LOGIN_SETUP_NONE_OPTION: "\u2014 Ninguna \u2014",
789
+ LOGIN_SETUP_SUMMARY: "Fichero: {file} \xB7 before: {before} \xB7 beforeEach: {beforeEach}",
790
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Guardar",
791
+ LOGIN_SETUP_CLEAR_BTN: "Eliminar configuraci\xF3n",
792
+ LOGIN_SETUP_CANCEL_BTN: "Cancelar",
793
+ LOGIN_SETUP_SOURCE_LABEL: "Origen del fichero de login",
794
+ LOGIN_SETUP_CREATE_SUB: "Genera un scaffold con funciones de ejemplo",
795
+ LOGIN_SETUP_SELECT_SUB: "Apunta a un servicio de login existente",
796
+ LOGIN_SETUP_INJECT_TITLE: "\xBFA\xF1adir bloques de login?",
797
+ LOGIN_SETUP_INJECT_TEXT: "Este fichero no tiene los bloques de login configurados. \xBFQuieres a\xF1adir el import y los bloques before/beforeEach ahora?",
798
+ LOGIN_SETUP_INJECT_CONFIRM: "S\xED, a\xF1adir"
736
799
  },
737
800
  SELECTOR_PICKER: {
738
801
  TITLE: "Selecciona un elemento del DOM",
@@ -823,6 +886,9 @@ var I18N_ES = {
823
886
  FILE_SAVE_ERROR_TOAST: "Error al guardar el archivo",
824
887
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures escritos en cypress/fixtures",
825
888
  FIXTURES_NO_FOLDER_TOAST: "No se pudieron escribir los fixtures (configura la carpeta Cypress)",
889
+ FILE_UPLOAD_MANUAL_TOAST: "A\xF1ade {files} a cypress/fixtures/ antes de ejecutar el test.",
890
+ FILE_UPLOAD_COPIED_TOAST: "{files} copiado/s a cypress/fixtures/.",
891
+ FILE_UPLOAD_COPY_ERROR_TOAST: "No se pudo copiar {files} a cypress/fixtures/. A\xF1\xE1delo manualmente.",
826
892
  BTN_CONFIG: "Config",
827
893
  BTN_FILES: "Ficheros",
828
894
  BTN_COMMANDS: "Comandos",
@@ -870,7 +936,7 @@ var I18N_ES = {
870
936
  SEC_PANELS: "\u{1F5C2} Paneles",
871
937
  PANEL_TESTS: "\u{1F4CB} Tests \u2014 guardados: filtrar por tag, copiar, combinar, exportar.",
872
938
  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.",
939
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 idioma, selector, HTTP, carpeta Cypress, login setup, datos, visibilidad, posici\xF3n.",
874
940
  PANEL_FILES: "\u{1F4C1} Ficheros \u2014 inserta tests en tus .cy.ts y lanza el runner.",
875
941
  SEC_SELECTOR: "\u{1F50E} Selectores",
876
942
  SELECTOR_1: "Elige el atributo preferido (data-cy, data-testid, aria-label, id) en Config.",
@@ -883,6 +949,7 @@ var I18N_ES = {
883
949
  ADV_RUNNER: "Runner local: npx lib-e2e-cypress-runner para lanzar un spec headless desde el editor.",
884
950
  ADV_CROSSAPP: "Cross-app: la grabaci\xF3n sobrevive al cambiar de micro-frontend (mismo origen).",
885
951
  ADV_DRAG: "Arrastra el bot\xF3n \u23FA para mover el widget; recuerda su posici\xF3n.",
952
+ ADV_LOGIN: "Login Setup (Ctrl+3): apunta a tu servicio de login; el grabador inyecta before()/beforeEach() autom\xE1ticamente en cada test nuevo.",
886
953
  CLOSE: "Cerrar",
887
954
  TAB_REFERENCE: "Referencia r\xE1pida",
888
955
  TAB_GUIDE: "Gu\xEDa de uso",
@@ -900,13 +967,13 @@ var I18N_ES = {
900
967
  G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
901
968
  G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
902
969
  G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
970
+ G_COV_7: "Login Setup: configura tu funci\xF3n de login en Ctrl+3 \u2192 Login Setup y el grabador a\xF1ade los bloques before()/beforeEach() a cada test nuevo.",
903
971
  G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
904
972
  G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
905
973
  G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
906
974
  G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
907
975
  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)."
976
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse."
910
977
  }
911
978
  };
912
979
 
@@ -937,7 +1004,10 @@ var I18N_EN = {
937
1004
  NOTES_PLACEHOLDER: "What this test validates, preconditions, etc.",
938
1005
  TICKET_LABEL: "Ticket / Issue (optional):",
939
1006
  TICKET_PLACEHOLDER: "E.g.: PROJ-123, #42",
940
- TICKET_WARN: "Invalid ID for the configured provider (will be saved anyway)"
1007
+ TICKET_WARN: "Invalid ID for the configured provider (will be saved anyway)",
1008
+ CONFIRM_DISCARD: "Are you sure you want to discard this recording? All recorded steps will be lost.",
1009
+ CONFIRM_DISCARD_BTN: "Yes, discard",
1010
+ BACK_BTN: "Go back"
941
1011
  },
942
1012
  TEST_EDITOR: {
943
1013
  NO_TAGS: "No tags",
@@ -1019,7 +1089,30 @@ var I18N_EN = {
1019
1089
  ISSUE_TRACKER_SUB: "Add a ticket ID when saving and generate a clickable link in the test list.",
1020
1090
  ISSUE_TRACKER_PROVIDER: "Provider",
1021
1091
  ISSUE_TRACKER_BASE_URL: "Base URL",
1022
- ISSUE_TRACKER_BASE_URL_PH: "E.g.: https://myproject.atlassian.net"
1092
+ ISSUE_TRACKER_BASE_URL_PH: "E.g.: https://myproject.atlassian.net",
1093
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
1094
+ LOGIN_SETUP_NOT_CONFIGURED: "Not configured",
1095
+ LOGIN_SETUP_SET_UP_BTN: "Set up",
1096
+ LOGIN_SETUP_TITLE: "Login setup",
1097
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Create scaffold",
1098
+ LOGIN_SETUP_SELECT_EXISTING: "Select existing file",
1099
+ LOGIN_SETUP_FILE_NAME_LABEL: "File name",
1100
+ LOGIN_SETUP_FILE_NAME_PH: "E.g.: cypress/common-services/login.service.ts",
1101
+ LOGIN_SETUP_RESCAN: "\u{1F504} Re-scan",
1102
+ LOGIN_SETUP_NO_FUNCTIONS: "No exported functions found. Fill in the scaffold and re-scan.",
1103
+ LOGIN_SETUP_BEFORE_LABEL: "before() function (once per suite)",
1104
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "beforeEach() function (before every test)",
1105
+ LOGIN_SETUP_NONE_OPTION: "\u2014 None \u2014",
1106
+ LOGIN_SETUP_SUMMARY: "File: {file} \xB7 before: {before} \xB7 beforeEach: {beforeEach}",
1107
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Save",
1108
+ LOGIN_SETUP_CLEAR_BTN: "Remove configuration",
1109
+ LOGIN_SETUP_CANCEL_BTN: "Cancel",
1110
+ LOGIN_SETUP_SOURCE_LABEL: "Login file source",
1111
+ LOGIN_SETUP_CREATE_SUB: "Generate a scaffold with sample functions",
1112
+ LOGIN_SETUP_SELECT_SUB: "Point to an existing login service file",
1113
+ LOGIN_SETUP_INJECT_TITLE: "Add login blocks?",
1114
+ LOGIN_SETUP_INJECT_TEXT: "This file does not have the configured login blocks. Do you want to add the import and before/beforeEach blocks now?",
1115
+ LOGIN_SETUP_INJECT_CONFIRM: "Yes, add them"
1023
1116
  },
1024
1117
  SELECTOR_PICKER: {
1025
1118
  TITLE: "Select a DOM element",
@@ -1110,6 +1203,9 @@ var I18N_EN = {
1110
1203
  FILE_SAVE_ERROR_TOAST: "Error saving the file",
1111
1204
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures written to cypress/fixtures",
1112
1205
  FIXTURES_NO_FOLDER_TOAST: "Could not write fixtures (configure the Cypress folder)",
1206
+ FILE_UPLOAD_MANUAL_TOAST: "Add {files} to cypress/fixtures/ before running the test.",
1207
+ FILE_UPLOAD_COPIED_TOAST: "{files} copied to cypress/fixtures/.",
1208
+ FILE_UPLOAD_COPY_ERROR_TOAST: "Could not copy {files} to cypress/fixtures/. Add it manually.",
1113
1209
  BTN_CONFIG: "Config",
1114
1210
  BTN_FILES: "Files",
1115
1211
  BTN_COMMANDS: "Commands",
@@ -1157,7 +1253,7 @@ var I18N_EN = {
1157
1253
  SEC_PANELS: "\u{1F5C2} Panels",
1158
1254
  PANEL_TESTS: "\u{1F4CB} Tests \u2014 saved tests: filter by tag, copy, combine, export.",
1159
1255
  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.",
1256
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 language, selector, HTTP, Cypress folder, login setup, data, visibility, position.",
1161
1257
  PANEL_FILES: "\u{1F4C1} Files \u2014 insert tests into your .cy.ts and launch the runner.",
1162
1258
  SEC_SELECTOR: "\u{1F50E} Selectors",
1163
1259
  SELECTOR_1: "Choose the preferred attribute (data-cy, data-testid, aria-label, id) in Config.",
@@ -1170,6 +1266,7 @@ var I18N_EN = {
1170
1266
  ADV_RUNNER: "Local runner: npx lib-e2e-cypress-runner to run a spec headless from the editor.",
1171
1267
  ADV_CROSSAPP: "Cross-app: the recording survives switching micro-frontend (same origin).",
1172
1268
  ADV_DRAG: "Drag the \u23FA button to move the widget; its position is remembered.",
1269
+ ADV_LOGIN: "Login Setup (Ctrl+3): point to your login service; the recorder automatically injects before()/beforeEach() blocks into each new test.",
1173
1270
  CLOSE: "Close",
1174
1271
  TAB_REFERENCE: "Quick reference",
1175
1272
  TAB_GUIDE: "Usage guide",
@@ -1187,13 +1284,13 @@ var I18N_EN = {
1187
1284
  G_COV_4: "Reliable selector (data-cy/testid/aria/id) and a picker when none is obvious.",
1188
1285
  G_COV_5: "Automatic assertions (Alt+click) and the manual builder.",
1189
1286
  G_COV_6: "Continuity when switching micro-frontend (same origin), invisible mode and i18n.",
1287
+ G_COV_7: "Login Setup: configure your login function under Ctrl+3 \u2192 Login Setup; the recorder adds before()/beforeEach() blocks to every new test.",
1190
1288
  G_SEC_NOTCOVERED: "\u{1F6AB} What it does NOT cover (yet)",
1191
1289
  G_NC_1: "Different origins / subdomains (same origin only).",
1192
1290
  G_NC_2: "File upload (selectFile), drag & drop, and hover.",
1193
1291
  G_NC_3: "The Tab key (Cypress can't .type() it).",
1194
1292
  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)."
1293
+ G_NC_5: "Third-party iframes and Shadow DOM may not be captured."
1197
1294
  }
1198
1295
  };
1199
1296
 
@@ -1224,8 +1321,11 @@ var I18N_FR = {
1224
1321
  NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc.",
1225
1322
  TICKET_LABEL: "Ticket / Issue (optionnel) :",
1226
1323
  TICKET_PLACEHOLDER: "Ex : PROJ-123, #42",
1227
- TICKET_WARN: "ID non valide pour le fournisseur configur\xE9 (sera enregistr\xE9 quand m\xEAme)"
1324
+ TICKET_WARN: "ID non valide pour le fournisseur configur\xE9 (sera enregistr\xE9 quand m\xEAme)",
1228
1325
  // TODO: translate
1326
+ CONFIRM_DISCARD: "\xCAtes-vous s\xFBr de vouloir abandonner cet enregistrement ? Toutes les \xE9tapes enregistr\xE9es seront perdues.",
1327
+ CONFIRM_DISCARD_BTN: "Oui, abandonner",
1328
+ BACK_BTN: "Retour"
1229
1329
  },
1230
1330
  TEST_EDITOR: {
1231
1331
  NO_TAGS: "Sans \xE9tiquettes",
@@ -1307,7 +1407,30 @@ var I18N_FR = {
1307
1407
  ISSUE_TRACKER_SUB: "Ajoutez un ID de ticket lors de l'enregistrement et g\xE9n\xE9rez un lien cliquable.",
1308
1408
  ISSUE_TRACKER_PROVIDER: "Fournisseur",
1309
1409
  ISSUE_TRACKER_BASE_URL: "URL de base",
1310
- ISSUE_TRACKER_BASE_URL_PH: "Ex : https://monprojet.atlassian.net"
1410
+ ISSUE_TRACKER_BASE_URL_PH: "Ex : https://monprojet.atlassian.net",
1411
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
1412
+ LOGIN_SETUP_NOT_CONFIGURED: "Non configur\xE9",
1413
+ LOGIN_SETUP_SET_UP_BTN: "Configurer",
1414
+ LOGIN_SETUP_TITLE: "Configuration du login",
1415
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Cr\xE9er un scaffold",
1416
+ LOGIN_SETUP_SELECT_EXISTING: "S\xE9lectionner un fichier existant",
1417
+ LOGIN_SETUP_FILE_NAME_LABEL: "Nom du fichier",
1418
+ LOGIN_SETUP_FILE_NAME_PH: "Ex : cypress/common-services/login.service.ts",
1419
+ LOGIN_SETUP_RESCAN: "\u{1F504} Re-scanner",
1420
+ LOGIN_SETUP_NO_FUNCTIONS: "Aucune fonction export\xE9e trouv\xE9e. Remplissez le scaffold et re-scannez.",
1421
+ LOGIN_SETUP_BEFORE_LABEL: "Fonction before() (une fois par suite)",
1422
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "Fonction beforeEach() (avant chaque test)",
1423
+ LOGIN_SETUP_NONE_OPTION: "\u2014 Aucune \u2014",
1424
+ LOGIN_SETUP_SUMMARY: "Fichier : {file} \xB7 before : {before} \xB7 beforeEach : {beforeEach}",
1425
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Enregistrer",
1426
+ LOGIN_SETUP_CLEAR_BTN: "Supprimer la configuration",
1427
+ LOGIN_SETUP_CANCEL_BTN: "Annuler",
1428
+ LOGIN_SETUP_SOURCE_LABEL: "Source du fichier de login",
1429
+ LOGIN_SETUP_CREATE_SUB: "G\xE9n\xE9rer un scaffold avec des fonctions exemples",
1430
+ LOGIN_SETUP_SELECT_SUB: "Pointer vers un fichier de service de login existant",
1431
+ LOGIN_SETUP_INJECT_TITLE: "Ajouter les blocs de login ?",
1432
+ LOGIN_SETUP_INJECT_TEXT: "Ce fichier ne contient pas les blocs de login configur\xE9s. Voulez-vous ajouter l'import et les blocs before/beforeEach maintenant ?",
1433
+ LOGIN_SETUP_INJECT_CONFIRM: "Oui, ajouter"
1311
1434
  },
1312
1435
  SELECTOR_PICKER: {
1313
1436
  TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
@@ -1398,6 +1521,12 @@ var I18N_FR = {
1398
1521
  FILE_SAVE_ERROR_TOAST: "Erreur lors de l'enregistrement",
1399
1522
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures \xE9crites dans cypress/fixtures",
1400
1523
  FIXTURES_NO_FOLDER_TOAST: "Impossible d'\xE9crire les fixtures (configure le dossier Cypress)",
1524
+ FILE_UPLOAD_MANUAL_TOAST: "Ajoutez {files} \xE0 cypress/fixtures/ avant d'ex\xE9cuter le test.",
1525
+ // TODO: translate
1526
+ FILE_UPLOAD_COPIED_TOAST: "{files} copi\xE9(s) dans cypress/fixtures/.",
1527
+ // TODO: translate
1528
+ FILE_UPLOAD_COPY_ERROR_TOAST: "Impossible de copier {files} vers cypress/fixtures/. Ajoutez-le manuellement.",
1529
+ // TODO: translate
1401
1530
  BTN_CONFIG: "Config",
1402
1531
  BTN_FILES: "Fichiers",
1403
1532
  BTN_COMMANDS: "Commandes",
@@ -1445,7 +1574,7 @@ var I18N_FR = {
1445
1574
  SEC_PANELS: "\u{1F5C2} Panneaux",
1446
1575
  PANEL_TESTS: "\u{1F4CB} Tests \u2014 enregistr\xE9s : filtrer par tag, copier, combiner, exporter.",
1447
1576
  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.",
1577
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 langue, s\xE9lecteur, HTTP, dossier Cypress, login setup, donn\xE9es, visibilit\xE9, position.",
1449
1578
  PANEL_FILES: "\u{1F4C1} Fichiers \u2014 ins\xE8re des tests dans tes .cy.ts et lance le runner.",
1450
1579
  SEC_SELECTOR: "\u{1F50E} S\xE9lecteurs",
1451
1580
  SELECTOR_1: "Choisis l'attribut pr\xE9f\xE9r\xE9 (data-cy, data-testid, aria-label, id) dans Config.",
@@ -1458,6 +1587,8 @@ var I18N_FR = {
1458
1587
  ADV_RUNNER: "Runner local : npx lib-e2e-cypress-runner pour lancer un spec headless depuis l'\xE9diteur.",
1459
1588
  ADV_CROSSAPP: "Cross-app : l'enregistrement survit au changement de micro-frontend (m\xEAme origine).",
1460
1589
  ADV_DRAG: "Fais glisser le bouton \u23FA pour d\xE9placer le widget ; sa position est m\xE9moris\xE9e.",
1590
+ ADV_LOGIN: "Login Setup (Ctrl+3): apunta a tu servicio de login; el grabador inyecta before()/beforeEach() autom\xE1ticamente en cada test nuevo.",
1591
+ // TODO: translate (fr)
1461
1592
  CLOSE: "Fermer",
1462
1593
  TAB_REFERENCE: "R\xE9f\xE9rence rapide",
1463
1594
  TAB_GUIDE: "Guide d'utilisation",
@@ -1476,13 +1607,14 @@ var I18N_FR = {
1476
1607
  G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
1477
1608
  G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
1478
1609
  G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
1610
+ G_COV_7: "Login Setup: configura tu funci\xF3n de login en Ctrl+3 \u2192 Login Setup y el grabador a\xF1ade los bloques before()/beforeEach() a cada test nuevo.",
1611
+ // TODO: translate (fr)
1479
1612
  G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
1480
1613
  G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
1481
1614
  G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
1482
1615
  G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
1483
1616
  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)."
1617
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse."
1486
1618
  }
1487
1619
  };
1488
1620
 
@@ -1513,8 +1645,11 @@ var I18N_IT = {
1513
1645
  NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc.",
1514
1646
  TICKET_LABEL: "Ticket / Issue (opzionale):",
1515
1647
  TICKET_PLACEHOLDER: "Es: PROJ-123, #42",
1516
- TICKET_WARN: "ID non valido per il provider configurato (verr\xE0 salvato comunque)"
1648
+ TICKET_WARN: "ID non valido per il provider configurato (verr\xE0 salvato comunque)",
1517
1649
  // TODO: translate
1650
+ CONFIRM_DISCARD: "Sei sicuro di voler scartare questa registrazione? Tutti i passi registrati andranno persi.",
1651
+ CONFIRM_DISCARD_BTN: "S\xEC, scarta",
1652
+ BACK_BTN: "Indietro"
1518
1653
  },
1519
1654
  TEST_EDITOR: {
1520
1655
  NO_TAGS: "Nessuna etichetta",
@@ -1596,7 +1731,30 @@ var I18N_IT = {
1596
1731
  ISSUE_TRACKER_SUB: "Aggiungi un ID ticket al salvataggio e genera un link cliccabile nell'elenco.",
1597
1732
  ISSUE_TRACKER_PROVIDER: "Provider",
1598
1733
  ISSUE_TRACKER_BASE_URL: "URL base",
1599
- ISSUE_TRACKER_BASE_URL_PH: "Es: https://mioprogetto.atlassian.net"
1734
+ ISSUE_TRACKER_BASE_URL_PH: "Es: https://mioprogetto.atlassian.net",
1735
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
1736
+ LOGIN_SETUP_NOT_CONFIGURED: "Non configurato",
1737
+ LOGIN_SETUP_SET_UP_BTN: "Configura",
1738
+ LOGIN_SETUP_TITLE: "Configurazione del login",
1739
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Crea scaffold",
1740
+ LOGIN_SETUP_SELECT_EXISTING: "Seleziona file esistente",
1741
+ LOGIN_SETUP_FILE_NAME_LABEL: "Nome del file",
1742
+ LOGIN_SETUP_FILE_NAME_PH: "Es: cypress/common-services/login.service.ts",
1743
+ LOGIN_SETUP_RESCAN: "\u{1F504} Re-scansiona",
1744
+ LOGIN_SETUP_NO_FUNCTIONS: "Nessuna funzione esportata trovata. Compila lo scaffold e ri-scansiona.",
1745
+ LOGIN_SETUP_BEFORE_LABEL: "Funzione before() (una volta per suite)",
1746
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "Funzione beforeEach() (prima di ogni test)",
1747
+ LOGIN_SETUP_NONE_OPTION: "\u2014 Nessuna \u2014",
1748
+ LOGIN_SETUP_SUMMARY: "File: {file} \xB7 before: {before} \xB7 beforeEach: {beforeEach}",
1749
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Salva",
1750
+ LOGIN_SETUP_CLEAR_BTN: "Rimuovi configurazione",
1751
+ LOGIN_SETUP_CANCEL_BTN: "Annulla",
1752
+ LOGIN_SETUP_SOURCE_LABEL: "Sorgente del file di login",
1753
+ LOGIN_SETUP_CREATE_SUB: "Genera uno scaffold con funzioni di esempio",
1754
+ LOGIN_SETUP_SELECT_SUB: "Punta a un file di servizio di login esistente",
1755
+ LOGIN_SETUP_INJECT_TITLE: "Aggiungere i blocchi di login?",
1756
+ LOGIN_SETUP_INJECT_TEXT: "Questo file non contiene i blocchi di login configurati. Vuoi aggiungere l'import e i blocchi before/beforeEach ora?",
1757
+ LOGIN_SETUP_INJECT_CONFIRM: "S\xEC, aggiungi"
1600
1758
  },
1601
1759
  SELECTOR_PICKER: {
1602
1760
  TITLE: "Seleziona un elemento DOM",
@@ -1687,6 +1845,12 @@ var I18N_IT = {
1687
1845
  FILE_SAVE_ERROR_TOAST: "Errore durante il salvataggio",
1688
1846
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixture scritte in cypress/fixtures",
1689
1847
  FIXTURES_NO_FOLDER_TOAST: "Impossibile scrivere le fixture (configura la cartella Cypress)",
1848
+ FILE_UPLOAD_MANUAL_TOAST: "Aggiungi {files} a cypress/fixtures/ prima di eseguire il test.",
1849
+ // TODO: translate
1850
+ FILE_UPLOAD_COPIED_TOAST: "{files} copiato/i in cypress/fixtures/.",
1851
+ // TODO: translate
1852
+ FILE_UPLOAD_COPY_ERROR_TOAST: "Impossibile copiare {files} in cypress/fixtures/. Aggiungilo manualmente.",
1853
+ // TODO: translate
1690
1854
  BTN_CONFIG: "Config",
1691
1855
  BTN_FILES: "File",
1692
1856
  BTN_COMMANDS: "Comandi",
@@ -1734,7 +1898,7 @@ var I18N_IT = {
1734
1898
  SEC_PANELS: "\u{1F5C2} Pannelli",
1735
1899
  PANEL_TESTS: "\u{1F4CB} Test \u2014 salvati: filtra per tag, copia, combina, esporta.",
1736
1900
  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.",
1901
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 lingua, selettore, HTTP, cartella Cypress, login setup, dati, visibilit\xE0, posizione.",
1738
1902
  PANEL_FILES: "\u{1F4C1} File \u2014 inserisci test nei tuoi .cy.ts e avvia il runner.",
1739
1903
  SEC_SELECTOR: "\u{1F50E} Selettori",
1740
1904
  SELECTOR_1: "Scegli l'attributo preferito (data-cy, data-testid, aria-label, id) in Config.",
@@ -1747,6 +1911,8 @@ var I18N_IT = {
1747
1911
  ADV_RUNNER: "Runner locale: npx lib-e2e-cypress-runner per eseguire uno spec headless dall'editor.",
1748
1912
  ADV_CROSSAPP: "Cross-app: la registrazione sopravvive al cambio di micro-frontend (stessa origine).",
1749
1913
  ADV_DRAG: "Trascina il pulsante \u23FA per spostare il widget; la posizione viene ricordata.",
1914
+ ADV_LOGIN: "Login Setup (Ctrl+3): apunta a tu servicio de login; el grabador inyecta before()/beforeEach() autom\xE1ticamente en cada test nuevo.",
1915
+ // TODO: translate (it)
1750
1916
  CLOSE: "Chiudi",
1751
1917
  TAB_REFERENCE: "Riferimento rapido",
1752
1918
  TAB_GUIDE: "Guida all'uso",
@@ -1765,13 +1931,14 @@ var I18N_IT = {
1765
1931
  G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
1766
1932
  G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
1767
1933
  G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
1934
+ G_COV_7: "Login Setup: configura tu funci\xF3n de login en Ctrl+3 \u2192 Login Setup y el grabador a\xF1ade los bloques before()/beforeEach() a cada test nuevo.",
1935
+ // TODO: translate (it)
1768
1936
  G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
1769
1937
  G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
1770
1938
  G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
1771
1939
  G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
1772
1940
  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)."
1941
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse."
1775
1942
  }
1776
1943
  };
1777
1944
 
@@ -1802,8 +1969,11 @@ var I18N_DE = {
1802
1969
  NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw.",
1803
1970
  TICKET_LABEL: "Ticket / Issue (optional):",
1804
1971
  TICKET_PLACEHOLDER: "Z. B.: PROJ-123, #42",
1805
- TICKET_WARN: "Ung\xFCltige ID f\xFCr den konfigurierten Anbieter (wird trotzdem gespeichert)"
1972
+ TICKET_WARN: "Ung\xFCltige ID f\xFCr den konfigurierten Anbieter (wird trotzdem gespeichert)",
1806
1973
  // TODO: translate
1974
+ CONFIRM_DISCARD: "Sind Sie sicher, dass Sie diese Aufzeichnung verwerfen m\xF6chten? Alle aufgezeichneten Schritte gehen verloren.",
1975
+ CONFIRM_DISCARD_BTN: "Ja, verwerfen",
1976
+ BACK_BTN: "Zur\xFCck"
1807
1977
  },
1808
1978
  TEST_EDITOR: {
1809
1979
  NO_TAGS: "Keine Tags",
@@ -1885,7 +2055,30 @@ var I18N_DE = {
1885
2055
  ISSUE_TRACKER_SUB: "F\xFCge beim Speichern eine Ticket-ID hinzu und erstelle einen klickbaren Link.",
1886
2056
  ISSUE_TRACKER_PROVIDER: "Anbieter",
1887
2057
  ISSUE_TRACKER_BASE_URL: "Basis-URL",
1888
- ISSUE_TRACKER_BASE_URL_PH: "Z. B.: https://meinprojekt.atlassian.net"
2058
+ ISSUE_TRACKER_BASE_URL_PH: "Z. B.: https://meinprojekt.atlassian.net",
2059
+ LOGIN_SETUP_SECTION: "\u{1F510} Login setup",
2060
+ LOGIN_SETUP_NOT_CONFIGURED: "Nicht konfiguriert",
2061
+ LOGIN_SETUP_SET_UP_BTN: "Einrichten",
2062
+ LOGIN_SETUP_TITLE: "Login-Konfiguration",
2063
+ LOGIN_SETUP_CREATE_SCAFFOLD: "Scaffold erstellen",
2064
+ LOGIN_SETUP_SELECT_EXISTING: "Vorhandene Datei ausw\xE4hlen",
2065
+ LOGIN_SETUP_FILE_NAME_LABEL: "Dateiname",
2066
+ LOGIN_SETUP_FILE_NAME_PH: "Z. B.: cypress/common-services/login.service.ts",
2067
+ LOGIN_SETUP_RESCAN: "\u{1F504} Erneut scannen",
2068
+ LOGIN_SETUP_NO_FUNCTIONS: "Keine exportierten Funktionen gefunden. Scaffold ausf\xFCllen und erneut scannen.",
2069
+ LOGIN_SETUP_BEFORE_LABEL: "before()-Funktion (einmal pro Suite)",
2070
+ LOGIN_SETUP_BEFORE_EACH_LABEL: "beforeEach()-Funktion (vor jedem Test)",
2071
+ LOGIN_SETUP_NONE_OPTION: "\u2014 Keine \u2014",
2072
+ LOGIN_SETUP_SUMMARY: "Datei: {file} \xB7 before: {before} \xB7 beforeEach: {beforeEach}",
2073
+ LOGIN_SETUP_SAVE_BTN: "\u{1F4BE} Speichern",
2074
+ LOGIN_SETUP_CLEAR_BTN: "Konfiguration entfernen",
2075
+ LOGIN_SETUP_CANCEL_BTN: "Abbrechen",
2076
+ LOGIN_SETUP_SOURCE_LABEL: "Quelle der Login-Datei",
2077
+ LOGIN_SETUP_CREATE_SUB: "Scaffold mit Beispielfunktionen erstellen",
2078
+ LOGIN_SETUP_SELECT_SUB: "Auf eine vorhandene Login-Service-Datei verweisen",
2079
+ LOGIN_SETUP_INJECT_TITLE: "Login-Bl\xF6cke hinzuf\xFCgen?",
2080
+ LOGIN_SETUP_INJECT_TEXT: "Diese Datei enth\xE4lt die konfigurierten Login-Bl\xF6cke nicht. M\xF6chten Sie jetzt den Import und die before/beforeEach-Bl\xF6cke hinzuf\xFCgen?",
2081
+ LOGIN_SETUP_INJECT_CONFIRM: "Ja, hinzuf\xFCgen"
1889
2082
  },
1890
2083
  SELECTOR_PICKER: {
1891
2084
  TITLE: "DOM-Element ausw\xE4hlen",
@@ -1976,6 +2169,12 @@ var I18N_DE = {
1976
2169
  FILE_SAVE_ERROR_TOAST: "Fehler beim Speichern",
1977
2170
  FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures in cypress/fixtures geschrieben",
1978
2171
  FIXTURES_NO_FOLDER_TOAST: "Fixtures konnten nicht geschrieben werden (Cypress-Ordner konfigurieren)",
2172
+ FILE_UPLOAD_MANUAL_TOAST: "F\xFCge {files} zu cypress/fixtures/ hinzu, bevor du den Test ausf\xFChrst.",
2173
+ // TODO: translate
2174
+ FILE_UPLOAD_COPIED_TOAST: "{files} nach cypress/fixtures/ kopiert.",
2175
+ // TODO: translate
2176
+ FILE_UPLOAD_COPY_ERROR_TOAST: "{files} konnte nicht nach cypress/fixtures/ kopiert werden. F\xFCge es manuell hinzu.",
2177
+ // TODO: translate
1979
2178
  BTN_CONFIG: "Config",
1980
2179
  BTN_FILES: "Dateien",
1981
2180
  BTN_COMMANDS: "Befehle",
@@ -2023,7 +2222,7 @@ var I18N_DE = {
2023
2222
  SEC_PANELS: "\u{1F5C2} Panels",
2024
2223
  PANEL_TESTS: "\u{1F4CB} Tests \u2014 gespeichert: nach Tag filtern, kopieren, kombinieren, exportieren.",
2025
2224
  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.",
2225
+ PANEL_CONFIG: "\u2699\uFE0F Config \u2014 Sprache, Selektor, HTTP, Cypress-Ordner, Login-Setup, Daten, Sichtbarkeit, Position.",
2027
2226
  PANEL_FILES: "\u{1F4C1} Dateien \u2014 Tests in deine .cy.ts einf\xFCgen und den Runner starten.",
2028
2227
  SEC_SELECTOR: "\u{1F50E} Selektoren",
2029
2228
  SELECTOR_1: "W\xE4hle das bevorzugte Attribut (data-cy, data-testid, aria-label, id) unter Config.",
@@ -2036,6 +2235,8 @@ var I18N_DE = {
2036
2235
  ADV_RUNNER: "Lokaler Runner: npx lib-e2e-cypress-runner, um einen Spec headless aus dem Editor zu starten.",
2037
2236
  ADV_CROSSAPP: "Cross-App: die Aufzeichnung \xFCbersteht den Wechsel des Micro-Frontends (gleicher Origin).",
2038
2237
  ADV_DRAG: "Ziehe den \u23FA-Button, um das Widget zu verschieben; die Position wird gemerkt.",
2238
+ ADV_LOGIN: "Login Setup (Ctrl+3): apunta a tu servicio de login; el grabador inyecta before()/beforeEach() autom\xE1ticamente en cada test nuevo.",
2239
+ // TODO: translate (de)
2039
2240
  CLOSE: "Schlie\xDFen",
2040
2241
  TAB_REFERENCE: "Kurzreferenz",
2041
2242
  TAB_GUIDE: "Nutzungsleitfaden",
@@ -2054,13 +2255,14 @@ var I18N_DE = {
2054
2255
  G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
2055
2256
  G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
2056
2257
  G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
2258
+ G_COV_7: "Login Setup: configura tu funci\xF3n de login en Ctrl+3 \u2192 Login Setup y el grabador a\xF1ade los bloques before()/beforeEach() a cada test nuevo.",
2259
+ // TODO: translate (de)
2057
2260
  G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
2058
2261
  G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
2059
2262
  G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
2060
2263
  G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
2061
2264
  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)."
2265
+ G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse."
2064
2266
  }
2065
2267
  };
2066
2268
 
@@ -2103,34 +2305,8 @@ var TranslationService = class {
2103
2305
  };
2104
2306
  var translationService = new TranslationService();
2105
2307
 
2106
- // src/utils/code-format.utils.ts
2107
- function gcd(a, b) {
2108
- while (b) {
2109
- const t = b;
2110
- b = a % b;
2111
- a = t;
2112
- }
2113
- return a;
2114
- }
2115
- function escapeSingleQuotes(value) {
2116
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2117
- }
2118
- function normalizeBlock(code, baseIndent) {
2119
- const lines = code.split("\n").map((l) => l.trimEnd());
2120
- const nonEmpty = lines.filter((l) => l.trim().length > 0);
2121
- if (!nonEmpty.length) return "";
2122
- const indents = nonEmpty.map((l) => l.length - l.trimStart().length);
2123
- const minIndent = Math.min(...indents);
2124
- const relIndents = [...new Set(indents.map((n) => n - minIndent).filter((n) => n > 0))];
2125
- const step = relIndents.length > 0 ? relIndents.reduce(gcd) : 2;
2126
- return lines.map((l) => {
2127
- if (!l.trim()) return "";
2128
- const level = Math.round((l.length - l.trimStart().length - minIndent) / step);
2129
- return baseIndent + " ".repeat(level) + l.trimStart();
2130
- }).join("\n");
2131
- }
2132
-
2133
2308
  // src/services/transformation.service.ts
2309
+ init_code_format_utils();
2134
2310
  var TransformationService = class {
2135
2311
  toLang(lang) {
2136
2312
  return isLang(lang) ? lang : "en";
@@ -2195,6 +2371,7 @@ var advancedTestTransformationService = new AdvancedTestTransformationService();
2195
2371
  init_selector_quality_utils();
2196
2372
 
2197
2373
  // src/utils/assertion.utils.ts
2374
+ init_code_format_utils();
2198
2375
  var MAX_TEXT_LENGTH = 60;
2199
2376
  function inferAssertionCommand(el, selector) {
2200
2377
  const should = (body) => `cy.get('${selector}').should(${body})`;
@@ -2219,6 +2396,7 @@ function inferAssertionCommand(el, selector) {
2219
2396
  }
2220
2397
 
2221
2398
  // src/services/recording.service.ts
2399
+ init_code_format_utils();
2222
2400
  var OWN_SELECTOR = '[data-cy="lib-e2e-cypress-for-dummys"]';
2223
2401
  function createSessionId() {
2224
2402
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -2234,6 +2412,7 @@ var RecordingService = class {
2234
2412
  selectorNotFound$ = new Subject(null);
2235
2413
  inputDebounceTimers = /* @__PURE__ */ new Map();
2236
2414
  fixtures = /* @__PURE__ */ new Map();
2415
+ uploadedFiles = /* @__PURE__ */ new Map();
2237
2416
  abort = new AbortController();
2238
2417
  selectorStrategy = "data-cy";
2239
2418
  /** Stable id of the current live session (null when none has started). */
@@ -2260,7 +2439,7 @@ var RecordingService = class {
2260
2439
  this.isPaused$.next(false);
2261
2440
  this.isRecording$.next(true);
2262
2441
  this.addCommand(`cy.viewport(1900, 1200)`);
2263
- this.addCommand(`cy.visit('${window.location.pathname}')`);
2442
+ this.addCommand(`cy.visit('${escapeSingleQuotes(window.location.pathname)}')`);
2264
2443
  this.addCommand(`cy.get('[data-cy="lib-e2e-cypress-for-dummys"]').invoke('hide');`);
2265
2444
  }
2266
2445
  /**
@@ -2358,10 +2537,20 @@ var RecordingService = class {
2358
2537
  getFixturesSnapshot() {
2359
2538
  return [...this.fixtures.entries()].map(([name, content]) => ({ name, content }));
2360
2539
  }
2540
+ addUploadedFile(filename, bytes) {
2541
+ this.uploadedFiles.set(filename, bytes);
2542
+ }
2543
+ getUploadedFilesSnapshot() {
2544
+ return [...this.uploadedFiles.entries()].map(([filename, bytes]) => ({ filename, bytes }));
2545
+ }
2546
+ clearUploadedFiles() {
2547
+ this.uploadedFiles.clear();
2548
+ }
2361
2549
  clearCommands() {
2362
2550
  this.commands$.next([]);
2363
2551
  this.interceptors$.next([]);
2364
2552
  this.fixtures.clear();
2553
+ this.uploadedFiles.clear();
2365
2554
  }
2366
2555
  clearInterceptors() {
2367
2556
  this.interceptors$.next([]);
@@ -2481,7 +2670,8 @@ var RecordingService = class {
2481
2670
  if (e.altKey) return;
2482
2671
  const selector = this.resolveSelectorFor(e.target);
2483
2672
  if (!selector) return;
2484
- const clickCmd = `cy.get('${selector}').click()`;
2673
+ const safeSelector = escapeSingleQuotes(selector);
2674
+ const clickCmd = `cy.get('${safeSelector}').click()`;
2485
2675
  let cmds = this.commands$.getValue();
2486
2676
  let removed = 0;
2487
2677
  while (removed < 2 && cmds.length > 0 && cmds[cmds.length - 1] === clickCmd) {
@@ -2489,7 +2679,7 @@ var RecordingService = class {
2489
2679
  removed++;
2490
2680
  }
2491
2681
  if (removed > 0) this.commands$.next(cmds);
2492
- this.addCommand(`cy.get('${selector}').dblclick()`);
2682
+ this.addCommand(`cy.get('${safeSelector}').dblclick()`);
2493
2683
  },
2494
2684
  { signal: this.abort.signal }
2495
2685
  );
@@ -2501,7 +2691,7 @@ var RecordingService = class {
2501
2691
  if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
2502
2692
  const selector = this.resolveSelectorFor(e.target);
2503
2693
  if (!selector) return;
2504
- this.addCommand(`cy.get('${selector}').rightclick()`);
2694
+ this.addCommand(`cy.get('${escapeSingleQuotes(selector)}').rightclick()`);
2505
2695
  },
2506
2696
  { signal: this.abort.signal }
2507
2697
  );
@@ -2521,7 +2711,7 @@ var RecordingService = class {
2521
2711
  if (!selector) return;
2522
2712
  this.flushInputDebounce(target);
2523
2713
  const token = key === "Enter" ? "{enter}" : "{esc}";
2524
- this.addCommand(`cy.get('${selector}').type('${token}')`);
2714
+ this.addCommand(`cy.get('${escapeSingleQuotes(selector)}').type('${token}')`);
2525
2715
  },
2526
2716
  { signal: this.abort.signal }
2527
2717
  );
@@ -2545,19 +2735,52 @@ var RecordingService = class {
2545
2735
  if (!this.isRecording$.getValue()) return;
2546
2736
  const target = e.target;
2547
2737
  if (!target || this.isOwnElement(target)) return;
2548
- if (target.tagName.toLowerCase() === "select") {
2738
+ const tag = target.tagName.toLowerCase();
2739
+ if (tag === "select") {
2549
2740
  this.handleSelectEvent(target);
2741
+ } else if (tag === "input") {
2742
+ const type = (target.getAttribute("type") ?? "").toLowerCase();
2743
+ if (type === "file") {
2744
+ void this.handleFileInputChange(target);
2745
+ }
2550
2746
  }
2551
2747
  },
2552
2748
  { signal: this.abort.signal }
2553
2749
  );
2554
2750
  }
2751
+ async handleFileInputChange(target) {
2752
+ if (this.isPaused$.getValue()) return;
2753
+ const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2754
+ if (!container) return;
2755
+ const selector = this.getReliableSelector(container);
2756
+ if (!selector || this.isOwnSelector(selector)) return;
2757
+ const files = Array.from(target.files ?? []);
2758
+ if (!files.length) return;
2759
+ const paths = files.map((f) => `'cypress/fixtures/${escapeSingleQuotes(f.name)}'`);
2760
+ const pathArg = paths.length === 1 ? paths[0] : `[${paths.join(", ")}]`;
2761
+ this.addCommand(`cy.get('${escapeSingleQuotes(selector)}').selectFile(${pathArg})`);
2762
+ for (const file of files) {
2763
+ const bytes = await this.readFileAsArrayBuffer(file);
2764
+ this.addUploadedFile(file.name, bytes);
2765
+ }
2766
+ }
2767
+ readFileAsArrayBuffer(file) {
2768
+ if (typeof file.arrayBuffer === "function") {
2769
+ return file.arrayBuffer();
2770
+ }
2771
+ return new Promise((resolve, reject) => {
2772
+ const reader = new FileReader();
2773
+ reader.onload = () => resolve(reader.result);
2774
+ reader.onerror = () => reject(reader.error);
2775
+ reader.readAsArrayBuffer(file);
2776
+ });
2777
+ }
2555
2778
  listenToRouteChanges() {
2556
2779
  let lastUrl = window.location.pathname + window.location.search + window.location.hash;
2557
2780
  const addUrlCommand = (newUrl) => {
2558
2781
  if (!this.isRecording$.getValue()) return;
2559
2782
  if (newUrl === lastUrl) return;
2560
- this.addCommand(`cy.url().should('include', '${newUrl}')`);
2783
+ this.addCommand(`cy.url().should('include', '${escapeSingleQuotes(newUrl)}')`);
2561
2784
  lastUrl = newUrl;
2562
2785
  };
2563
2786
  const wrapMethod = (type) => {
@@ -2658,7 +2881,8 @@ var RecordingService = class {
2658
2881
  if (matSelect) dataCy = matSelect.closest("[data-cy]")?.getAttribute("data-cy") ?? null;
2659
2882
  if (!dataCy) dataCy = target.closest("[data-cy]")?.getAttribute("data-cy") ?? null;
2660
2883
  if (dataCy) {
2661
- this.addCommand(`cy.get('[data-cy="${dataCy}"]').click()`);
2884
+ const safeSel = escapeSingleQuotes(`[data-cy="${escapeCssAttrValue(dataCy)}"]`);
2885
+ this.addCommand(`cy.get('${safeSel}').click()`);
2662
2886
  return;
2663
2887
  }
2664
2888
  if (selector) {
@@ -2690,7 +2914,7 @@ var RecordingService = class {
2690
2914
  const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2691
2915
  if (!container) return;
2692
2916
  const selector = this.getReliableSelector(container);
2693
- const value = target.value.replace(/'/g, "\\'");
2917
+ const value = escapeSingleQuotes(target.value);
2694
2918
  this.addGenericCommand({
2695
2919
  selector,
2696
2920
  action: (s) => `cy.get('${s}').clear().type('${value}')`
@@ -2709,7 +2933,7 @@ var RecordingService = class {
2709
2933
  const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
2710
2934
  if (!container) return;
2711
2935
  const selector = this.getReliableSelector(container);
2712
- const value = target.value.replace(/'/g, "\\'");
2936
+ const value = escapeSingleQuotes(target.value);
2713
2937
  this.addGenericCommand({
2714
2938
  selector,
2715
2939
  action: (s) => `cy.get('${s}').select('${value}')`
@@ -2718,7 +2942,7 @@ var RecordingService = class {
2718
2942
  // ── Shared helpers ────────────────────────────────────────────────────────
2719
2943
  addGenericCommand(opts) {
2720
2944
  if (!opts.selector || this.isOwnSelector(opts.selector)) return;
2721
- this.addCommand(opts.action(opts.selector));
2945
+ this.addCommand(opts.action(escapeSingleQuotes(opts.selector)));
2722
2946
  }
2723
2947
  getReliableSelector(el) {
2724
2948
  const strategy = this.selectorStrategy;
@@ -2730,20 +2954,20 @@ var RecordingService = class {
2730
2954
  const validId = rawId && rawId.length < 25 && /^[a-zA-Z][\w-]*$/.test(rawId) && !FORBIDDEN_ID_PREFIXES.some((p) => rawId.startsWith(p)) && !/^\d+$/.test(rawId) ? rawId : null;
2731
2955
  switch (strategy) {
2732
2956
  case "data-testid":
2733
- if (dataTestid) return `[data-testid="${dataTestid}"]`;
2734
- if (dataCy) return `[data-cy="${dataCy}"]`;
2957
+ if (dataTestid) return `[data-testid="${escapeCssAttrValue(dataTestid)}"]`;
2958
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
2735
2959
  return validId ? `#${validId}` : null;
2736
2960
  case "aria-label":
2737
- if (ariaLabel) return `[aria-label="${ariaLabel}"]`;
2738
- if (dataCy) return `[data-cy="${dataCy}"]`;
2961
+ if (ariaLabel) return `[aria-label="${escapeCssAttrValue(ariaLabel)}"]`;
2962
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
2739
2963
  return validId ? `#${validId}` : null;
2740
2964
  case "id":
2741
2965
  if (validId) return `#${validId}`;
2742
- if (dataCy) return `[data-cy="${dataCy}"]`;
2743
- return dataTestid ? `[data-testid="${dataTestid}"]` : null;
2966
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
2967
+ return dataTestid ? `[data-testid="${escapeCssAttrValue(dataTestid)}"]` : null;
2744
2968
  default:
2745
- if (dataCy) return `[data-cy="${dataCy}"]`;
2746
- if (dataDotCy) return `[data.cy="${dataDotCy}"]`;
2969
+ if (dataCy) return `[data-cy="${escapeCssAttrValue(dataCy)}"]`;
2970
+ if (dataDotCy) return `[data.cy="${escapeCssAttrValue(dataDotCy)}"]`;
2747
2971
  return validId ? `#${validId}` : null;
2748
2972
  }
2749
2973
  }
@@ -2769,8 +2993,41 @@ var RecordingService = class {
2769
2993
 
2770
2994
  // src/services/persistence.service.ts
2771
2995
  var import_idb = require("idb");
2996
+ init_code_format_utils();
2997
+
2998
+ // src/models/issue-tracker.model.ts
2999
+ var ISSUE_TRACKER_PRESETS = {
3000
+ jira: { id: "jira", label: "Jira", urlPattern: "{baseUrl}/browse/{id}", idPattern: /^[A-Z][A-Z0-9]+-\d+$/ },
3001
+ github: { id: "github", label: "GitHub Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
3002
+ gitlab: { id: "gitlab", label: "GitLab Issues", urlPattern: "{baseUrl}/-/issues/{id}", idPattern: /^\d+$/ },
3003
+ azure: { id: "azure", label: "Azure DevOps", urlPattern: "{baseUrl}/_workitems/edit/{id}", idPattern: /^\d+$/ },
3004
+ bitbucket: { id: "bitbucket", label: "Bitbucket Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
3005
+ custom: { id: "custom", label: "Custom", urlPattern: "{baseUrl}/{id}", idPattern: /.+/ }
3006
+ };
3007
+ var DEFAULT_ISSUE_TRACKER_CONFIG = {
3008
+ enabled: false,
3009
+ provider: "jira",
3010
+ baseUrl: ""
3011
+ };
3012
+
3013
+ // src/utils/ticket.utils.ts
3014
+ function buildTicketUrl(ticketId, config) {
3015
+ if (!config.enabled || !ticketId.trim() || !config.baseUrl.trim()) return null;
3016
+ const preset = ISSUE_TRACKER_PRESETS[config.provider];
3017
+ const base = config.baseUrl.replace(/\/+$/, "");
3018
+ return preset.urlPattern.replace("{baseUrl}", base).replace("{id}", encodeURIComponent(ticketId.trim()));
3019
+ }
3020
+ function buildTicketComment(ticketId) {
3021
+ return `// Ticket: ${ticketId.trim()}`;
3022
+ }
3023
+ function isValidTicketId(ticketId, provider) {
3024
+ if (!ticketId.trim()) return false;
3025
+ return ISSUE_TRACKER_PRESETS[provider].idPattern.test(ticketId.trim());
3026
+ }
3027
+
3028
+ // src/services/persistence.service.ts
2772
3029
  var ACTIVE_SESSION_ID = 1;
2773
- var PersistenceService = class {
3030
+ var PersistenceService = class _PersistenceService {
2774
3031
  // dbName is overridable so tests can use unique names for isolation.
2775
3032
  constructor(dbName = DB_SCHEMA.name) {
2776
3033
  this.dbName = dbName;
@@ -2823,7 +3080,8 @@ var PersistenceService = class {
2823
3080
  if (!record) return null;
2824
3081
  const commands = await this.getCommandStrings(testId);
2825
3082
  const interceptors = await this.getInterceptorStrings(testId);
2826
- const itBlock = `it('${escapeSingleQuotes(record.name)}', () => {
3083
+ const ticketComment = record.ticketId ? buildTicketComment(record.ticketId) + "\n" : "";
3084
+ const itBlock = `${ticketComment}it('${escapeSingleQuotes(record.name)}', () => {
2827
3085
  ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2828
3086
  });`;
2829
3087
  const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
@@ -2904,6 +3162,30 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2904
3162
  const records = await db.getAll("configuration");
2905
3163
  return records[0] ?? null;
2906
3164
  }
3165
+ // ── Login setup config ───────────────────────────────────────────────────
3166
+ static LOGIN_SETUP_KEY = "loginSetupConfig";
3167
+ async saveLoginSetup(config) {
3168
+ await this.setConfigKey(_PersistenceService.LOGIN_SETUP_KEY, JSON.stringify(config));
3169
+ }
3170
+ async getLoginSetup() {
3171
+ const record = await this.getConfig(_PersistenceService.LOGIN_SETUP_KEY);
3172
+ if (!record) return null;
3173
+ const raw = record[_PersistenceService.LOGIN_SETUP_KEY];
3174
+ if (typeof raw !== "string") return null;
3175
+ try {
3176
+ return JSON.parse(raw);
3177
+ } catch {
3178
+ return null;
3179
+ }
3180
+ }
3181
+ async clearLoginSetup() {
3182
+ const db = await this.getDB();
3183
+ const records = await db.getAll("configuration");
3184
+ if (!records.length) return;
3185
+ const current = { ...records[0] };
3186
+ delete current[_PersistenceService.LOGIN_SETUP_KEY];
3187
+ await db.put("configuration", current);
3188
+ }
2907
3189
  // ── Active recording session ──────────────────────────────────────────────
2908
3190
  /** Upserts the live recording session (single record, fixed key). */
2909
3191
  async saveActiveSession(state) {
@@ -2946,6 +3228,24 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2946
3228
  }
2947
3229
  await this.bulkInsertWithoutId("interceptors", interceptors);
2948
3230
  }
3231
+ async hasDirectoryAccess() {
3232
+ const config = await this.getGeneralConfig();
3233
+ return !!config?.["cypressDirectoryHandle"];
3234
+ }
3235
+ async writeUploadedFile(filename, bytes) {
3236
+ const config = await this.getGeneralConfig();
3237
+ const dirHandle = config?.["cypressDirectoryHandle"];
3238
+ if (!dirHandle) throw new Error("No Cypress folder configured");
3239
+ const perm = dirHandle;
3240
+ let state = await perm.queryPermission({ mode: "readwrite" });
3241
+ if (state !== "granted") state = await perm.requestPermission({ mode: "readwrite" });
3242
+ if (state !== "granted") throw new Error("Write permission denied");
3243
+ const fixturesDir = await dirHandle.getDirectoryHandle("fixtures", { create: true });
3244
+ const fileHandle = await fixturesDir.getFileHandle(filename, { create: true });
3245
+ const writable = await fileHandle.createWritable();
3246
+ await writable.write(bytes);
3247
+ await writable.close();
3248
+ }
2949
3249
  async requestDirectoryPermissions() {
2950
3250
  if (!("showDirectoryPicker" in window)) {
2951
3251
  throw new Error("File System Access API not supported");
@@ -2988,7 +3288,36 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
2988
3288
  }
2989
3289
  }
2990
3290
  };
2991
- var persistenceService = new PersistenceService();
3291
+
3292
+ // src/services/http-monitor.ts
3293
+ init_code_format_utils();
3294
+
3295
+ // src/utils/redact.utils.ts
3296
+ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
3297
+ "password",
3298
+ "token",
3299
+ "secret",
3300
+ "authorization",
3301
+ "cookie",
3302
+ "access_token",
3303
+ "refresh_token"
3304
+ ]);
3305
+ function isSensitiveKey(key) {
3306
+ return SENSITIVE_KEYS.has(key.toLowerCase());
3307
+ }
3308
+ function redactSensitiveFields(obj) {
3309
+ if (Array.isArray(obj)) {
3310
+ return obj.map(redactSensitiveFields);
3311
+ }
3312
+ if (obj !== null && typeof obj === "object") {
3313
+ const result = {};
3314
+ for (const [key, value] of Object.entries(obj)) {
3315
+ result[key] = isSensitiveKey(key) ? "[REDACTED]" : redactSensitiveFields(value);
3316
+ }
3317
+ return result;
3318
+ }
3319
+ return obj;
3320
+ }
2992
3321
 
2993
3322
  // src/services/http-monitor.ts
2994
3323
  var INTERCEPTED_METHODS = ["GET", "POST", "PUT"];
@@ -3001,8 +3330,12 @@ function generateAlias(method, url) {
3001
3330
  return `${method.toLowerCase()}-intercepted-request`;
3002
3331
  }
3003
3332
  }
3333
+ var SAFE_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
3004
3334
  function buildValidations(base, obj) {
3005
- return Object.keys(obj).filter((key) => key !== "id" && key !== "uid").map((key) => `expect(${base}.${key}).to.equal(${JSON.stringify(obj[key])});`).join("\n");
3335
+ return Object.keys(obj).filter((key) => key !== "id" && key !== "uid").map((key) => {
3336
+ const accessor = SAFE_IDENTIFIER.test(key) ? `.${key}` : `['${escapeSingleQuotes(key)}']`;
3337
+ return `expect(${base}${accessor}).to.equal(${JSON.stringify(obj[key])});`;
3338
+ }).join("\n");
3006
3339
  }
3007
3340
  function buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody) {
3008
3341
  if (extendedHttp && method === "GET" && responseBody) {
@@ -3054,20 +3387,149 @@ function resolveMethod(input, init) {
3054
3387
  if (input instanceof Request) return input.method.toUpperCase();
3055
3388
  return "GET";
3056
3389
  }
3390
+ async function _handleFetchInterception(recording, input, init, responseClone) {
3391
+ const method = resolveMethod(input, init);
3392
+ if (!INTERCEPTED_METHODS.includes(method)) return;
3393
+ const url = resolveUrl(input);
3394
+ const alias = generateAlias(method, url);
3395
+ recording.registerInterceptor(method, url, alias);
3396
+ const extendedHttp = localStorage.getItem("extendedHttpCommands") === "true";
3397
+ const fixtureMode = localStorage.getItem("fixtureMode") === "true";
3398
+ const requestBody = extendedHttp ? parseRequestBody(init) : null;
3399
+ let responseText = null;
3400
+ let responseBody = null;
3401
+ if (extendedHttp || fixtureMode && method === "GET") {
3402
+ try {
3403
+ responseText = await responseClone.text();
3404
+ if (extendedHttp) responseBody = parseJsonObject(responseText);
3405
+ } catch {
3406
+ }
3407
+ }
3408
+ if (fixtureMode && method === "GET" && responseText !== null) {
3409
+ const pretty = prettyJsonOrNull(responseText);
3410
+ if (pretty !== null) {
3411
+ const redacted = redactSensitiveFields(JSON.parse(pretty));
3412
+ recording.registerFixture(`${alias}.json`, JSON.stringify(redacted, null, 2));
3413
+ }
3414
+ }
3415
+ const cmd = buildCyWaitCommand(
3416
+ method,
3417
+ alias,
3418
+ extendedHttp,
3419
+ requestBody ? redactSensitiveFields(requestBody) : null,
3420
+ responseBody ? redactSensitiveFields(responseBody) : null
3421
+ );
3422
+ recording.addCommand(cmd);
3423
+ }
3424
+ function _handleXhrInterception(recording, method, url, requestBody, responseText) {
3425
+ if (!INTERCEPTED_METHODS.includes(method)) return;
3426
+ const alias = generateAlias(method, url);
3427
+ recording.registerInterceptor(method, url, alias);
3428
+ if (localStorage.getItem("fixtureMode") === "true" && method === "GET") {
3429
+ const pretty = prettyJsonOrNull(responseText);
3430
+ if (pretty !== null) {
3431
+ const redacted = redactSensitiveFields(JSON.parse(pretty));
3432
+ recording.registerFixture(`${alias}.json`, JSON.stringify(redacted, null, 2));
3433
+ }
3434
+ }
3435
+ const extendedHttp = localStorage.getItem("extendedHttpCommands") === "true";
3436
+ const responseBody = extendedHttp ? parseJsonObject(responseText) : null;
3437
+ const cmd = buildCyWaitCommand(
3438
+ method,
3439
+ alias,
3440
+ extendedHttp,
3441
+ requestBody ? redactSensitiveFields(requestBody) : null,
3442
+ responseBody ? redactSensitiveFields(responseBody) : null
3443
+ );
3444
+ recording.addCommand(cmd);
3445
+ }
3446
+ var _refCount = 0;
3447
+ var _originalFetch = null;
3448
+ var _originalXHR = null;
3449
+ var _recordings = /* @__PURE__ */ new Set();
3450
+ function _patchFetch() {
3451
+ const orig = window.fetch;
3452
+ _originalFetch = orig;
3453
+ window.fetch = async (input, init) => {
3454
+ const response = await orig(input, init);
3455
+ try {
3456
+ const active = [..._recordings];
3457
+ if (active.length > 0) {
3458
+ await Promise.all(
3459
+ active.map((rec) => _handleFetchInterception(rec, input, init, response.clone()).catch(() => {
3460
+ }))
3461
+ );
3462
+ }
3463
+ } catch {
3464
+ }
3465
+ return response;
3466
+ };
3467
+ }
3468
+ function _restoreFetch() {
3469
+ if (!_originalFetch) return;
3470
+ window.fetch = _originalFetch;
3471
+ _originalFetch = null;
3472
+ }
3473
+ function _patchXhr() {
3474
+ const OrigXHR = window.XMLHttpRequest;
3475
+ _originalXHR = OrigXHR;
3476
+ window.XMLHttpRequest = class extends OrigXHR {
3477
+ _xhrMethod = "GET";
3478
+ _xhrUrl = "";
3479
+ _xhrRequestBody = null;
3480
+ open(method, url, async = true, user, password) {
3481
+ this._xhrMethod = method.toUpperCase();
3482
+ this._xhrUrl = url instanceof URL ? url.href : url;
3483
+ super.open(method, url, async, user, password);
3484
+ }
3485
+ send(body) {
3486
+ if (typeof body === "string") {
3487
+ this._xhrRequestBody = parseJsonObject(body);
3488
+ }
3489
+ this.addEventListener("load", () => {
3490
+ try {
3491
+ for (const rec of _recordings) {
3492
+ _handleXhrInterception(rec, this._xhrMethod, this._xhrUrl, this._xhrRequestBody, this.responseText);
3493
+ }
3494
+ } catch {
3495
+ }
3496
+ });
3497
+ super.send(body);
3498
+ }
3499
+ };
3500
+ }
3501
+ function _restoreXhr() {
3502
+ if (!_originalXHR) return;
3503
+ window.XMLHttpRequest = _originalXHR;
3504
+ _originalXHR = null;
3505
+ }
3506
+ function _resetHttpMonitorState() {
3507
+ _refCount = 0;
3508
+ _originalFetch = null;
3509
+ _originalXHR = null;
3510
+ _recordings.clear();
3511
+ }
3057
3512
  var HttpMonitor = class {
3058
3513
  constructor(recording) {
3059
3514
  this.recording = recording;
3060
3515
  }
3061
3516
  recording;
3062
- originalFetch = null;
3063
- originalXHR = null;
3064
3517
  install() {
3065
- this.installFetch();
3066
- this.installXhr();
3518
+ _recordings.add(this.recording);
3519
+ _refCount++;
3520
+ if (_refCount === 1) {
3521
+ _patchFetch();
3522
+ _patchXhr();
3523
+ }
3067
3524
  }
3068
3525
  uninstall() {
3069
- this.uninstallFetch();
3070
- this.uninstallXhr();
3526
+ _recordings.delete(this.recording);
3527
+ if (_refCount <= 0) return;
3528
+ _refCount--;
3529
+ if (_refCount === 0) {
3530
+ _restoreFetch();
3531
+ _restoreXhr();
3532
+ }
3071
3533
  }
3072
3534
  isExtendedHttpEnabled() {
3073
3535
  return localStorage.getItem("extendedHttpCommands") === "true";
@@ -3075,100 +3537,6 @@ var HttpMonitor = class {
3075
3537
  isFixtureModeEnabled() {
3076
3538
  return localStorage.getItem("fixtureMode") === "true";
3077
3539
  }
3078
- installFetch() {
3079
- if (this.originalFetch) return;
3080
- const originalFetch = window.fetch;
3081
- this.originalFetch = originalFetch;
3082
- window.fetch = async (input, init) => {
3083
- const response = await originalFetch(input, init);
3084
- try {
3085
- await this.handleFetchInterception(input, init, response.clone());
3086
- } catch {
3087
- }
3088
- return response;
3089
- };
3090
- }
3091
- uninstallFetch() {
3092
- if (!this.originalFetch) return;
3093
- window.fetch = this.originalFetch;
3094
- this.originalFetch = null;
3095
- }
3096
- installXhr() {
3097
- if (this.originalXHR) return;
3098
- this.originalXHR = window.XMLHttpRequest;
3099
- const OrigXHR = this.originalXHR;
3100
- const handleXhrInterception = this.handleXhrInterception.bind(this);
3101
- window.XMLHttpRequest = class extends OrigXHR {
3102
- _xhrMethod = "GET";
3103
- _xhrUrl = "";
3104
- _xhrRequestBody = null;
3105
- open(method, url, async = true, user, password) {
3106
- this._xhrMethod = method.toUpperCase();
3107
- this._xhrUrl = url instanceof URL ? url.href : url;
3108
- super.open(method, url, async, user, password);
3109
- }
3110
- send(body) {
3111
- if (typeof body === "string") {
3112
- this._xhrRequestBody = parseJsonObject(body);
3113
- }
3114
- this.addEventListener("load", () => {
3115
- try {
3116
- handleXhrInterception(
3117
- this._xhrMethod,
3118
- this._xhrUrl,
3119
- this._xhrRequestBody,
3120
- this.responseText
3121
- );
3122
- } catch {
3123
- }
3124
- });
3125
- super.send(body);
3126
- }
3127
- };
3128
- }
3129
- uninstallXhr() {
3130
- if (!this.originalXHR) return;
3131
- window.XMLHttpRequest = this.originalXHR;
3132
- this.originalXHR = null;
3133
- }
3134
- async handleFetchInterception(input, init, responseClone) {
3135
- const method = resolveMethod(input, init);
3136
- if (!INTERCEPTED_METHODS.includes(method)) return;
3137
- const url = resolveUrl(input);
3138
- const alias = generateAlias(method, url);
3139
- this.recording.registerInterceptor(method, url, alias);
3140
- const extendedHttp = this.isExtendedHttpEnabled();
3141
- const fixtureMode = this.isFixtureModeEnabled();
3142
- const requestBody = extendedHttp ? parseRequestBody(init) : null;
3143
- let responseText = null;
3144
- let responseBody = null;
3145
- if (extendedHttp || fixtureMode && method === "GET") {
3146
- try {
3147
- responseText = await responseClone.text();
3148
- if (extendedHttp) responseBody = parseJsonObject(responseText);
3149
- } catch {
3150
- }
3151
- }
3152
- if (fixtureMode && method === "GET" && responseText !== null) {
3153
- const pretty = prettyJsonOrNull(responseText);
3154
- if (pretty !== null) this.recording.registerFixture(`${alias}.json`, pretty);
3155
- }
3156
- const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
3157
- this.recording.addCommand(cmd);
3158
- }
3159
- handleXhrInterception(method, url, requestBody, responseText) {
3160
- if (!INTERCEPTED_METHODS.includes(method)) return;
3161
- const alias = generateAlias(method, url);
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
- }
3167
- const extendedHttp = this.isExtendedHttpEnabled();
3168
- const responseBody = extendedHttp ? parseJsonObject(responseText) : null;
3169
- const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
3170
- this.recording.addCommand(cmd);
3171
- }
3172
3540
  };
3173
3541
 
3174
3542
  // src/utils/styles.utils.ts
@@ -3417,6 +3785,19 @@ function setSwal2DataCyAttribute(dataCy = "lib-e2e-cypress-for-dummys") {
3417
3785
  document.querySelector(".swal2-html-container")?.setAttribute("data-cy", dataCy);
3418
3786
  document.querySelector(".swal2-title")?.setAttribute("data-cy", dataCy);
3419
3787
  }
3788
+ function ensurePopupDimensions(popup, heightPx) {
3789
+ if (!popup) return;
3790
+ popup.style.height = `${heightPx}px`;
3791
+ const htmlContainer = popup.querySelector(".swal2-html-container");
3792
+ if (!htmlContainer) return;
3793
+ htmlContainer.style.flex = "1";
3794
+ htmlContainer.style.minHeight = "0";
3795
+ htmlContainer.style.overflow = "hidden";
3796
+ htmlContainer.style.padding = "0";
3797
+ htmlContainer.style.margin = "0";
3798
+ htmlContainer.style.display = "flex";
3799
+ htmlContainer.style.flexDirection = "column";
3800
+ }
3420
3801
 
3421
3802
  // src/utils/toast.utils.ts
3422
3803
  function showToast(message, isSuccess = true) {
@@ -3689,35 +4070,8 @@ var TestPrevisualizerElement = class extends HTMLElement {
3689
4070
  }
3690
4071
  }
3691
4072
  };
3692
- if (!customElements.get("test-previsualizer")) {
3693
- customElements.define("test-previsualizer", TestPrevisualizerElement);
3694
- }
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());
4073
+ if (!customElements.get("lib-e2e-test-previsualizer")) {
4074
+ customElements.define("lib-e2e-test-previsualizer", TestPrevisualizerElement);
3721
4075
  }
3722
4076
 
3723
4077
  // src/components/save-test/save-test.styles.ts
@@ -3781,10 +4135,21 @@ var SAVE_TEST_STYLES = `
3781
4135
  transition: color 0.12s; padding: 0; background: none; border: none;
3782
4136
  }
3783
4137
  .chip-del:hover { color: #f85149; filter: none; }
4138
+ .discard-warn { color: #e6edf3; font-size: 13px; margin-bottom: 20px; line-height: 1.5; }
3784
4139
  `;
3785
4140
 
3786
4141
  // src/components/save-test/save-test.template.ts
3787
4142
  init_html_utils();
4143
+ function renderSaveTestConfirmDiscard(t) {
4144
+ return `
4145
+ <div class="container">
4146
+ <p class="discard-warn">\u26A0 ${t("SAVE_TEST.CONFIRM_DISCARD")}</p>
4147
+ <div class="btn-row">
4148
+ <button class="btn-danger" id="btn-confirm-discard">${t("SAVE_TEST.CONFIRM_DISCARD_BTN")}</button>
4149
+ <button id="btn-back-discard">${t("SAVE_TEST.BACK_BTN")}</button>
4150
+ </div>
4151
+ </div>`;
4152
+ }
3788
4153
  function renderSaveTestAsk(t) {
3789
4154
  return `
3790
4155
  <div class="container">
@@ -3830,6 +4195,7 @@ function renderSaveTestDesc(description, notes, tags, ticketId, issueTrackerEnab
3830
4195
  var SaveTestElement = class extends HTMLElement {
3831
4196
  shadow;
3832
4197
  _step = "ask";
4198
+ _stepBeforeDiscard = "ask";
3833
4199
  description = "";
3834
4200
  notes = "";
3835
4201
  tags = [];
@@ -3857,8 +4223,17 @@ var SaveTestElement = class extends HTMLElement {
3857
4223
  this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() });
3858
4224
  }
3859
4225
  cancel() {
4226
+ this._stepBeforeDiscard = this._step === "confirm-discard" ? "ask" : this._step;
4227
+ this._step = "confirm-discard";
4228
+ this.render();
4229
+ }
4230
+ confirmDiscard() {
3860
4231
  this.dispatch("savetest", { description: null, notes: "", tags: [], ticketId: "" });
3861
4232
  }
4233
+ backFromDiscard() {
4234
+ this._step = this._stepBeforeDiscard;
4235
+ this.render();
4236
+ }
3862
4237
  restartComponent() {
3863
4238
  this._step = "ask";
3864
4239
  this.description = "";
@@ -3889,6 +4264,12 @@ var SaveTestElement = class extends HTMLElement {
3889
4264
  this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
3890
4265
  }
3891
4266
  render() {
4267
+ if (this._step === "confirm-discard") {
4268
+ this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestConfirmDiscard(this.t.bind(this))}`;
4269
+ this.shadow.getElementById("btn-confirm-discard")?.addEventListener("click", () => this.confirmDiscard());
4270
+ this.shadow.getElementById("btn-back-discard")?.addEventListener("click", () => this.backFromDiscard());
4271
+ return;
4272
+ }
3892
4273
  if (this._step === "ask") {
3893
4274
  this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestAsk(this.t.bind(this))}`;
3894
4275
  this.shadow.getElementById("btn-yes")?.addEventListener("click", () => this.askSave());
@@ -3930,10 +4311,13 @@ var SaveTestElement = class extends HTMLElement {
3930
4311
  }
3931
4312
  }
3932
4313
  };
3933
- if (!customElements.get("save-test")) {
3934
- customElements.define("save-test", SaveTestElement);
4314
+ if (!customElements.get("lib-e2e-save-test")) {
4315
+ customElements.define("lib-e2e-save-test", SaveTestElement);
3935
4316
  }
3936
4317
 
4318
+ // src/components/test-editor/test-editor.ts
4319
+ init_code_format_utils();
4320
+
3937
4321
  // src/components/test-editor/test-editor.styles.ts
3938
4322
  var TEST_EDITOR_STYLES = `
3939
4323
  :host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
@@ -4156,6 +4540,7 @@ function highlightLine(line) {
4156
4540
  }
4157
4541
 
4158
4542
  // src/components/test-editor/test-editor.template.ts
4543
+ init_code_format_utils();
4159
4544
  function renderTestRow(test, i, state, t) {
4160
4545
  const { selectMode, selectedIds, expandedIndex, interceptorsByTest, locale, issueTrackerConfig } = state;
4161
4546
  const expanded = expandedIndex === i;
@@ -4164,7 +4549,9 @@ function renderTestRow(test, i, state, t) {
4164
4549
  const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
4165
4550
  const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
4166
4551
  const hasIcps = (test.interceptors ?? []).length > 0;
4167
- const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
4552
+ const ticketId = test.ticketId;
4553
+ const ticketComment = ticketId ? buildTicketComment(ticketId) + "\n" : "";
4554
+ const itBlockCode = `${ticketComment}it('${escapeSingleQuotes(test.name)}', () => {
4168
4555
  ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
4169
4556
  });`;
4170
4557
  const icpBlockCode = icps.length ? `beforeEach(() => {
@@ -4172,7 +4559,6 @@ ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
4172
4559
  ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
4173
4560
  });` : "";
4174
4561
  const notesHtml = expanded && test.notes ? `<p class="test-notes">${escHtml(test.notes)}</p>` : "";
4175
- const ticketId = test.ticketId;
4176
4562
  const ticketUrl = ticketId ? buildTicketUrl(ticketId, issueTrackerConfig) : null;
4177
4563
  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
4564
  const body = expanded ? `
@@ -4421,8 +4807,85 @@ ${beforeEach}${itBlocks}
4421
4807
  });
4422
4808
  }
4423
4809
  };
4424
- if (!customElements.get("test-editor")) {
4425
- customElements.define("test-editor", TestEditorElement);
4810
+ if (!customElements.get("lib-e2e-test-editor")) {
4811
+ customElements.define("lib-e2e-test-editor", TestEditorElement);
4812
+ }
4813
+
4814
+ // src/utils/login-setup.utils.ts
4815
+ var EXPORT_FN_RE = /^(?!\/\/).*\bexport\s+(?:async\s+)?function\s+(\w+)|^(?!\/\/).*\bexport\s+const\s+(\w+)\s*=\s*(?:async\s*)?\(/gm;
4816
+ function extractExportedFunctions(fileContent) {
4817
+ const names = [];
4818
+ let match;
4819
+ const re = new RegExp(EXPORT_FN_RE.source, EXPORT_FN_RE.flags);
4820
+ while ((match = re.exec(fileContent)) !== null) {
4821
+ const name = match[1] ?? match[2];
4822
+ if (name) names.push(name);
4823
+ }
4824
+ return names;
4825
+ }
4826
+ function buildLoginScaffold() {
4827
+ return [
4828
+ "/**",
4829
+ " * Login service scaffold \u2014 fill in your own auth logic.",
4830
+ " * This file is not committed to source control.",
4831
+ " */",
4832
+ "",
4833
+ "export function fetchAuthToken() {",
4834
+ " // TODO: implement \u2014 retrieve and return your auth token",
4835
+ "}",
4836
+ "",
4837
+ "export function setupRequestInterceptors() {",
4838
+ " // TODO: implement \u2014 configure cy.intercept() or set localStorage/cookies",
4839
+ "}",
4840
+ ""
4841
+ ].join("\n");
4842
+ }
4843
+ function buildLoginImportPath(fromFilePath, toServiceFilePath) {
4844
+ const fromParts = fromFilePath.replace(/\\/g, "/").split("/");
4845
+ const toParts = toServiceFilePath.replace(/\\/g, "/").split("/");
4846
+ fromParts.pop();
4847
+ let common = 0;
4848
+ while (common < fromParts.length && common < toParts.length - 1 && fromParts[common] === toParts[common]) {
4849
+ common++;
4850
+ }
4851
+ const ups = fromParts.length - common;
4852
+ const downs = toParts.slice(common);
4853
+ const serviceFile = downs[downs.length - 1].replace(/\.ts$/, "");
4854
+ const prefix = ups === 0 ? "." : Array(ups).fill("..").join("/");
4855
+ const middle = downs.slice(0, -1);
4856
+ return [prefix, ...middle, serviceFile].join("/");
4857
+ }
4858
+ function hasLoginBlocks(content, fnNames) {
4859
+ return fnNames.some((fn) => new RegExp(`\\b${fn}\\s*\\(`).test(content));
4860
+ }
4861
+ function injectLoginBlocksIntoExisting(content, importLine, beforeBlock, beforeEachBlock) {
4862
+ let result = content;
4863
+ if (importLine && !content.includes(importLine)) {
4864
+ result = `${importLine}
4865
+
4866
+ ${result}`;
4867
+ }
4868
+ const blocks = [beforeBlock, beforeEachBlock].filter(Boolean).join("");
4869
+ if (blocks) {
4870
+ const describeMatch = result.match(/(describe\s*\(.*?{)/s);
4871
+ if (describeMatch) {
4872
+ const insertPos = (describeMatch.index ?? 0) + describeMatch[0].length;
4873
+ result = result.slice(0, insertPos) + "\n" + blocks + result.slice(insertPos);
4874
+ }
4875
+ }
4876
+ return result;
4877
+ }
4878
+ function buildLoginBlocks(importPath, beforeFn, beforeEachFn) {
4879
+ if (!beforeFn && !beforeEachFn) {
4880
+ return { importLine: "", beforeBlock: "", beforeEachBlock: "" };
4881
+ }
4882
+ const fns = [...new Set([beforeFn, beforeEachFn].filter(Boolean))];
4883
+ const importLine = `import { ${fns.join(", ")} } from '${importPath}';`;
4884
+ const beforeBlock = beforeFn ? ` before(() => { ${beforeFn}(); });
4885
+ ` : "";
4886
+ const beforeEachBlock = beforeEachFn ? ` beforeEach(() => { ${beforeEachFn}(); });
4887
+ ` : "";
4888
+ return { importLine, beforeBlock, beforeEachBlock };
4426
4889
  }
4427
4890
 
4428
4891
  // src/components/configuration/configuration.styles.ts
@@ -4583,6 +5046,99 @@ var CONFIGURATION_STYLES = `
4583
5046
  .btn-export-confirm { background: #238636; border-color: #238636; color: #fff; }
4584
5047
  .btn-export-confirm:hover:not(:disabled) { background: #2ea043; border-color: #2ea043; }
4585
5048
  .btn-export-confirm:disabled { opacity: 0.45; cursor: not-allowed; }
5049
+
5050
+ /* \u2500\u2500 Form-element font reset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5051
+ input, select, button, textarea { font-family: inherit; }
5052
+
5053
+ /* \u2500\u2500 Primary action button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5054
+ .btn-primary { background: #1f6feb; border-color: #1f6feb; color: #fff; }
5055
+ .btn-primary:hover { background: #388bfd; border-color: #388bfd; color: #fff; }
5056
+ .btn-primary:disabled { opacity: 0.45; cursor: not-allowed; }
5057
+
5058
+ /* \u2500\u2500 Login Setup overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5059
+ .ls-modal { max-width: 500px; }
5060
+ .ls-hd { display: flex; align-items: center; gap: 8px; }
5061
+ .ls-hd-icon { font-size: 15px; }
5062
+
5063
+ .ls-body {
5064
+ padding: 20px; overflow-y: auto; flex: 1;
5065
+ display: flex; flex-direction: column; gap: 20px;
5066
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
5067
+ }
5068
+
5069
+ .ls-section { display: flex; flex-direction: column; gap: 8px; }
5070
+ .ls-section-label {
5071
+ font-size: 10px; font-weight: 700; text-transform: uppercase;
5072
+ letter-spacing: 0.8px; color: #484f58;
5073
+ }
5074
+
5075
+ .ls-actions { display: flex; gap: 10px; }
5076
+ .ls-action-btn {
5077
+ flex: 1; padding: 14px 12px; border-radius: 8px;
5078
+ background: #0d1117; border: 1px solid #21262d; color: #8b949e;
5079
+ display: flex; flex-direction: column; align-items: center; gap: 6px;
5080
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
5081
+ }
5082
+ .ls-action-btn:hover { background: #161b22; color: #e6edf3; border-color: #388bfd; }
5083
+ .ls-action-icon { font-size: 22px; line-height: 1; }
5084
+ .ls-action-label { font-size: 12px; font-weight: 600; color: #c9d1d9; }
5085
+ .ls-action-btn:hover .ls-action-label { color: #e6edf3; }
5086
+ .ls-action-sub { font-size: 10px; color: #484f58; line-height: 1.4; text-align: center; }
5087
+
5088
+ .ls-file-row {
5089
+ display: flex; align-items: center; gap: 10px;
5090
+ padding: 10px 12px; background: #0d1117;
5091
+ border: 1px solid #21262d; border-radius: 8px;
5092
+ }
5093
+ .ls-file-icon { font-size: 14px; flex-shrink: 0; }
5094
+ .ls-file-name {
5095
+ flex: 1; font-size: 11px; color: #c9d1d9;
5096
+ font-family: 'Cascadia Code','Fira Code','Consolas',monospace;
5097
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
5098
+ min-width: 0;
5099
+ }
5100
+ .ls-btn-rescan {
5101
+ flex-shrink: 0; padding: 4px 10px; font-size: 11px;
5102
+ background: #161b22; border-color: #30363d; color: #8b949e;
5103
+ }
5104
+ .ls-btn-rescan:hover { background: #21262d; color: #e6edf3; }
5105
+
5106
+ .ls-input {
5107
+ width: 100%; padding: 8px 10px; background: #0d1117;
5108
+ border: 1px solid #30363d; border-radius: 6px;
5109
+ color: #c9d1d9; font-size: 12px; outline: none; font-family: inherit;
5110
+ }
5111
+ .ls-input:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.12); }
5112
+
5113
+ .ls-fns {
5114
+ display: flex; flex-wrap: wrap; gap: 6px;
5115
+ padding: 10px; background: #0d1117;
5116
+ border: 1px solid #21262d; border-radius: 6px; min-height: 38px;
5117
+ }
5118
+ .ls-fn-chip {
5119
+ padding: 3px 10px; font-size: 11px; color: #79c0ff;
5120
+ background: rgba(47,129,247,0.1); border: 1px solid rgba(47,129,247,0.2);
5121
+ border-radius: 12px; font-family: 'Cascadia Code','Fira Code','Consolas',monospace;
5122
+ }
5123
+
5124
+ .ls-field-block { display: flex; flex-direction: column; gap: 6px; }
5125
+ .ls-field-block + .ls-field-block { margin-top: 12px; }
5126
+ .ls-label { font-size: 11px; color: #8b949e; }
5127
+ .ls-select {
5128
+ width: 100%; padding: 8px 10px; background: #0d1117;
5129
+ border: 1px solid #30363d; border-radius: 6px;
5130
+ color: #c9d1d9; font-size: 12px; outline: none; cursor: pointer;
5131
+ }
5132
+ .ls-select:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.12); }
5133
+
5134
+ .ls-empty-state {
5135
+ display: flex; align-items: center; gap: 10px;
5136
+ padding: 14px; background: #0d1117; border: 1px solid #21262d;
5137
+ border-radius: 8px; font-size: 12px; color: #484f58;
5138
+ }
5139
+ .ls-empty-icon { font-size: 16px; }
5140
+
5141
+ .ls-ft { padding: 14px 20px; }
4586
5142
  `;
4587
5143
 
4588
5144
  // src/components/configuration/configuration.template.ts
@@ -4800,6 +5356,13 @@ function renderConfiguration(state, t) {
4800
5356
  </div>` : ""}
4801
5357
  </div>
4802
5358
 
5359
+ <!-- Login Setup -->
5360
+ <div class="card card-wide">
5361
+ <div class="card-hd">${t("CONFIG.LOGIN_SETUP_SECTION")}</div>
5362
+ ${state.loginSetupConfig ? `<p style="font-size:12px;color:#8b949e;margin:0 0 8px">${escHtml(state.loginSetupConfig.filePath)}</p>` : `<p style="font-size:12px;color:#8b949e;margin:0 0 8px">${t("CONFIG.LOGIN_SETUP_NOT_CONFIGURED")}</p>`}
5363
+ <button id="btn-open-login-setup" class="btn-sm">${t("CONFIG.LOGIN_SETUP_SET_UP_BTN")}</button>
5364
+ </div>
5365
+
4803
5366
  <!-- Data -->
4804
5367
  <div class="card card-wide">
4805
5368
  <div class="card-hd">${t("CONFIG.DATA_SECTION")}</div>
@@ -4813,7 +5376,93 @@ function renderConfiguration(state, t) {
4813
5376
  </div>
4814
5377
  </div>
4815
5378
 
4816
- </div>${renderExportOverlay(state, t)}`;
5379
+ </div>${renderExportOverlay(state, t)}${renderLoginSetupOverlay(state, t)}`;
5380
+ }
5381
+ function renderLoginSetupOverlay(state, t) {
5382
+ if (!state.isLoginSetupOpen) return "";
5383
+ const cfg = state.loginSetupConfig;
5384
+ const path = state.loginSetupDraftPath ?? cfg?.filePath ?? "";
5385
+ const fns = state.loginSetupDraftFunctions ?? cfg?.detectedFunctions ?? [];
5386
+ const beforeFn = state.loginSetupDraftBeforeFn !== void 0 ? state.loginSetupDraftBeforeFn : cfg?.beforeFn ?? null;
5387
+ const beforeEachFn = state.loginSetupDraftBeforeEachFn !== void 0 ? state.loginSetupDraftBeforeEachFn : cfg?.beforeEachFn ?? null;
5388
+ const hasContent = state.loginSetupDraftHasContent ?? !!cfg?.fileContent;
5389
+ const fnOption = (name, selected) => `<option value="${escAttr(name ?? "")}" ${selected === name ? "selected" : ""}>${name ? escHtml(name) : t("CONFIG.LOGIN_SETUP_NONE_OPTION")}</option>`;
5390
+ const fnDropdown = (id, label, selected) => `
5391
+ <div class="ls-field-block">
5392
+ <label class="ls-label" for="${id}">${escHtml(label)}</label>
5393
+ <select id="${id}" class="ls-select">
5394
+ ${fnOption(null, selected)}
5395
+ ${fns.map((fn) => fnOption(fn, selected)).join("")}
5396
+ </select>
5397
+ </div>`;
5398
+ const functionsSection = hasContent ? fns.length > 0 ? `<div class="ls-section">
5399
+ <span class="ls-section-label">Funciones detectadas</span>
5400
+ <div class="ls-fns">${fns.map((fn) => `<span class="ls-fn-chip">${escHtml(fn)}</span>`).join("")}</div>
5401
+ </div>
5402
+ <div class="ls-section">
5403
+ <span class="ls-section-label">Asignaci\xF3n de bloques</span>
5404
+ ${fnDropdown("login-setup-before-fn", t("CONFIG.LOGIN_SETUP_BEFORE_LABEL"), beforeFn)}
5405
+ ${fnDropdown("login-setup-before-each-fn", t("CONFIG.LOGIN_SETUP_BEFORE_EACH_LABEL"), beforeEachFn)}
5406
+ </div>` : `<div class="ls-empty-state">
5407
+ <span class="ls-empty-icon">\u{1F50D}</span>
5408
+ <span>${t("CONFIG.LOGIN_SETUP_NO_FUNCTIONS")}</span>
5409
+ </div>` : "";
5410
+ const fileSection = hasContent ? `<div class="ls-section">
5411
+ <span class="ls-section-label">${t("CONFIG.LOGIN_SETUP_FILE_NAME_LABEL")}</span>
5412
+ <div class="ls-file-row">
5413
+ <span class="ls-file-icon">\u{1F4C4}</span>
5414
+ <span class="ls-file-name">${escHtml(path)}</span>
5415
+ <button id="btn-login-setup-rescan" class="ls-btn-rescan">${t("CONFIG.LOGIN_SETUP_RESCAN")}</button>
5416
+ </div>
5417
+ </div>` : `<div class="ls-section">
5418
+ <span class="ls-section-label">${t("CONFIG.LOGIN_SETUP_FILE_NAME_LABEL")}</span>
5419
+ <input id="login-setup-filepath" type="text" class="ls-input"
5420
+ placeholder="${escAttr(t("CONFIG.LOGIN_SETUP_FILE_NAME_PH"))}"
5421
+ value="${escAttr(path)}" />
5422
+ </div>`;
5423
+ return `
5424
+ <div class="export-overlay" id="login-setup-overlay">
5425
+ <div class="export-modal ls-modal">
5426
+
5427
+ <div class="export-hd ls-hd">
5428
+ <span class="ls-hd-icon">\u{1F510}</span>
5429
+ ${t("CONFIG.LOGIN_SETUP_TITLE")}
5430
+ </div>
5431
+
5432
+ <div class="ls-body">
5433
+
5434
+ <div class="ls-section">
5435
+ <span class="ls-section-label">${t("CONFIG.LOGIN_SETUP_SOURCE_LABEL")}</span>
5436
+ <div class="ls-actions">
5437
+ <button id="btn-login-setup-create-scaffold" class="ls-action-btn">
5438
+ <span class="ls-action-icon">\u2728</span>
5439
+ <span class="ls-action-label">${t("CONFIG.LOGIN_SETUP_CREATE_SCAFFOLD")}</span>
5440
+ <span class="ls-action-sub">${t("CONFIG.LOGIN_SETUP_CREATE_SUB")}</span>
5441
+ </button>
5442
+ <button id="btn-login-setup-pick-file" class="ls-action-btn">
5443
+ <span class="ls-action-icon">\u{1F4C2}</span>
5444
+ <span class="ls-action-label">${t("CONFIG.LOGIN_SETUP_SELECT_EXISTING")}</span>
5445
+ <span class="ls-action-sub">${t("CONFIG.LOGIN_SETUP_SELECT_SUB")}</span>
5446
+ </button>
5447
+ </div>
5448
+ </div>
5449
+
5450
+ ${fileSection}
5451
+
5452
+ ${functionsSection}
5453
+
5454
+ </div>
5455
+
5456
+ <div class="export-ft ls-ft">
5457
+ <button id="btn-login-setup-clear" class="btn-danger">${t("CONFIG.LOGIN_SETUP_CLEAR_BTN")}</button>
5458
+ <span class="export-ft-actions">
5459
+ <button id="btn-login-setup-cancel">${t("CONFIG.LOGIN_SETUP_CANCEL_BTN")}</button>
5460
+ <button id="btn-login-setup-save" class="btn-primary">${t("CONFIG.LOGIN_SETUP_SAVE_BTN")}</button>
5461
+ </span>
5462
+ </div>
5463
+
5464
+ </div>
5465
+ </div>`;
4817
5466
  }
4818
5467
 
4819
5468
  // src/components/configuration/configuration.ts
@@ -4834,6 +5483,15 @@ var ConfigurationElement = class extends HTMLElement {
4834
5483
  exportSelectedIds = /* @__PURE__ */ new Set();
4835
5484
  exportSelectedTags = /* @__PURE__ */ new Set();
4836
5485
  issueTrackerConfig = { ...DEFAULT_ISSUE_TRACKER_CONFIG };
5486
+ isLoginSetupOpen = false;
5487
+ loginSetupConfig = null;
5488
+ loginSetupDraftPath = "";
5489
+ loginSetupDraftFunctions = [];
5490
+ loginSetupDraftBeforeFn = null;
5491
+ loginSetupDraftBeforeEachFn = null;
5492
+ loginSetupDraftHasContent = false;
5493
+ loginSetupDraftHandle = null;
5494
+ loginSetupDraftContent = "";
4837
5495
  filesystemGranted = false;
4838
5496
  cypressFolderName = null;
4839
5497
  constructor() {
@@ -4875,6 +5533,7 @@ var ConfigurationElement = class extends HTMLElement {
4875
5533
  provider: config?.["issueTrackerProvider"] ?? DEFAULT_ISSUE_TRACKER_CONFIG.provider,
4876
5534
  baseUrl: config?.["issueTrackerBaseUrl"] ?? ""
4877
5535
  };
5536
+ this.loginSetupConfig = await this.persistence.getLoginSetup();
4878
5537
  this.render();
4879
5538
  }
4880
5539
  async onLanguageChange(lang) {
@@ -4940,6 +5599,85 @@ var ConfigurationElement = class extends HTMLElement {
4940
5599
  await this.persistence.setConfig({ issueTrackerBaseUrl: baseUrl });
4941
5600
  this.dispatchEvent(new CustomEvent("issuetrackerchange", { detail: { ...this.issueTrackerConfig }, bubbles: true, composed: true }));
4942
5601
  }
5602
+ openLoginSetupPanel() {
5603
+ this.isLoginSetupOpen = true;
5604
+ const cfg = this.loginSetupConfig;
5605
+ this.loginSetupDraftHandle = null;
5606
+ this.loginSetupDraftContent = cfg?.fileContent ?? "";
5607
+ this.loginSetupDraftPath = cfg?.filePath ?? "";
5608
+ this.loginSetupDraftFunctions = cfg?.detectedFunctions ?? [];
5609
+ this.loginSetupDraftBeforeFn = cfg?.beforeFn ?? null;
5610
+ this.loginSetupDraftBeforeEachFn = cfg?.beforeEachFn ?? null;
5611
+ this.loginSetupDraftHasContent = !!cfg?.fileContent;
5612
+ this.render();
5613
+ }
5614
+ closeLoginSetupPanel() {
5615
+ this.isLoginSetupOpen = false;
5616
+ this.render();
5617
+ }
5618
+ async saveLoginSetupConfig(config) {
5619
+ await this.persistence.saveLoginSetup(config);
5620
+ this.loginSetupConfig = config;
5621
+ this.isLoginSetupOpen = false;
5622
+ this.render();
5623
+ }
5624
+ async clearLoginSetupConfig() {
5625
+ await this.persistence.clearLoginSetup();
5626
+ this.loginSetupConfig = null;
5627
+ this.render();
5628
+ }
5629
+ async loginSetupPickFile() {
5630
+ const currentPath = this.shadow.getElementById("login-setup-filepath")?.value.trim() ?? "";
5631
+ try {
5632
+ const [handle] = await window.showOpenFilePicker({
5633
+ types: [{ description: "TypeScript files", accept: { "text/plain": [".ts"] } }],
5634
+ multiple: false
5635
+ });
5636
+ this.loginSetupDraftHandle = handle;
5637
+ const file = await handle.getFile();
5638
+ this.loginSetupDraftContent = await file.text();
5639
+ this.loginSetupDraftHasContent = true;
5640
+ this.loginSetupDraftPath = currentPath || `cypress/common-services/${handle.name}`;
5641
+ this.loginSetupDraftFunctions = extractExportedFunctions(this.loginSetupDraftContent);
5642
+ this.render();
5643
+ } catch (e) {
5644
+ if (e?.name !== "AbortError") showToast(this.t("CONFIG.FOLDER_ERROR_TOAST"), false);
5645
+ }
5646
+ }
5647
+ async loginSetupCreateScaffold() {
5648
+ const currentPath = this.shadow.getElementById("login-setup-filepath")?.value.trim() ?? "";
5649
+ try {
5650
+ const handle = await window.showSaveFilePicker({
5651
+ suggestedName: "login.service.ts",
5652
+ types: [{ description: "TypeScript files", accept: { "text/plain": [".ts"] } }]
5653
+ });
5654
+ const content = buildLoginScaffold();
5655
+ const writable = await handle.createWritable();
5656
+ await writable.write(content);
5657
+ await writable.close();
5658
+ this.loginSetupDraftHandle = handle;
5659
+ this.loginSetupDraftContent = content;
5660
+ this.loginSetupDraftHasContent = true;
5661
+ this.loginSetupDraftPath = currentPath || `cypress/common-services/${handle.name}`;
5662
+ this.loginSetupDraftFunctions = extractExportedFunctions(content);
5663
+ this.render();
5664
+ } catch (e) {
5665
+ if (e?.name !== "AbortError") showToast(this.t("CONFIG.FOLDER_ERROR_TOAST"), false);
5666
+ }
5667
+ }
5668
+ async loginSetupRescan() {
5669
+ const currentPath = this.shadow.getElementById("login-setup-filepath")?.value.trim();
5670
+ if (currentPath !== void 0) this.loginSetupDraftPath = currentPath;
5671
+ try {
5672
+ if (this.loginSetupDraftHandle) {
5673
+ const file = await this.loginSetupDraftHandle.getFile();
5674
+ this.loginSetupDraftContent = await file.text();
5675
+ }
5676
+ this.loginSetupDraftFunctions = extractExportedFunctions(this.loginSetupDraftContent);
5677
+ this.render();
5678
+ } catch {
5679
+ }
5680
+ }
4943
5681
  async changeFolder() {
4944
5682
  try {
4945
5683
  await this.persistence.requestDirectoryPermissions();
@@ -5042,7 +5780,14 @@ var ConfigurationElement = class extends HTMLElement {
5042
5780
  exportTests: this.exportTests,
5043
5781
  exportSelectedIds: this.exportSelectedIds,
5044
5782
  exportSelectedTags: this.exportSelectedTags,
5045
- issueTrackerConfig: this.issueTrackerConfig
5783
+ issueTrackerConfig: this.issueTrackerConfig,
5784
+ isLoginSetupOpen: this.isLoginSetupOpen,
5785
+ loginSetupConfig: this.loginSetupConfig,
5786
+ loginSetupDraftPath: this.loginSetupDraftPath,
5787
+ loginSetupDraftFunctions: this.loginSetupDraftFunctions,
5788
+ loginSetupDraftBeforeFn: this.loginSetupDraftBeforeFn,
5789
+ loginSetupDraftBeforeEachFn: this.loginSetupDraftBeforeEachFn,
5790
+ loginSetupDraftHasContent: this.loginSetupDraftHasContent
5046
5791
  }, this.t.bind(this))}`;
5047
5792
  ;
5048
5793
  this.shadow.getElementById("lang-select").addEventListener(
@@ -5101,24 +5846,48 @@ var ConfigurationElement = class extends HTMLElement {
5101
5846
  this.shadow.querySelectorAll("[data-export-tag]").forEach(
5102
5847
  (el) => el.addEventListener("click", () => this.toggleExportTag(el.dataset["exportTag"] ?? ""))
5103
5848
  );
5849
+ this.shadow.getElementById("btn-open-login-setup")?.addEventListener("click", () => this.openLoginSetupPanel());
5850
+ this.shadow.getElementById("btn-login-setup-cancel")?.addEventListener("click", () => this.closeLoginSetupPanel());
5851
+ this.shadow.getElementById("btn-login-setup-pick-file")?.addEventListener("click", () => this.loginSetupPickFile());
5852
+ this.shadow.getElementById("btn-login-setup-create-scaffold")?.addEventListener("click", () => this.loginSetupCreateScaffold());
5853
+ this.shadow.getElementById("btn-login-setup-rescan")?.addEventListener("click", () => this.loginSetupRescan());
5854
+ this.shadow.getElementById("btn-login-setup-clear")?.addEventListener("click", () => this.clearLoginSetupConfig());
5855
+ this.shadow.getElementById("btn-login-setup-save")?.addEventListener("click", async () => {
5856
+ const pathEl = this.shadow.getElementById("login-setup-filepath");
5857
+ const beforeEl = this.shadow.getElementById("login-setup-before-fn");
5858
+ const beforeEachEl = this.shadow.getElementById("login-setup-before-each-fn");
5859
+ await this.saveLoginSetupConfig({
5860
+ enabled: true,
5861
+ filePath: pathEl?.value.trim() ?? this.loginSetupDraftPath,
5862
+ fileContent: this.loginSetupDraftContent,
5863
+ detectedFunctions: this.loginSetupDraftFunctions,
5864
+ beforeFn: beforeEl?.value || null,
5865
+ beforeEachFn: beforeEachEl?.value || null
5866
+ });
5867
+ });
5868
+ ;
5104
5869
  this.shadow.getElementById("file-input").addEventListener("change", async (e) => {
5105
5870
  const file = e.target.files?.[0];
5106
5871
  if (!file) return;
5107
5872
  try {
5108
5873
  await this.importAllData(file);
5109
- alert(this.t("CONFIG.IMPORT_SUCCESS"));
5874
+ showToast(this.t("CONFIG.IMPORT_SUCCESS"));
5110
5875
  } catch (err) {
5111
- alert(err.message ?? this.t("CONFIG.IMPORT_ERROR"));
5876
+ showToast(err.message ?? this.t("CONFIG.IMPORT_ERROR"), false);
5112
5877
  }
5113
5878
  const target = e.target;
5114
5879
  if (target) target.value = "";
5115
5880
  });
5116
5881
  }
5117
5882
  };
5118
- if (!customElements.get("e2e-configuration")) {
5119
- customElements.define("e2e-configuration", ConfigurationElement);
5883
+ if (!customElements.get("lib-e2e-configuration")) {
5884
+ customElements.define("lib-e2e-configuration", ConfigurationElement);
5120
5885
  }
5121
5886
 
5887
+ // src/components/advanced-test-editor/advanced-test-editor.ts
5888
+ var import_sweetalert2 = __toESM(require("sweetalert2"), 1);
5889
+ init_code_format_utils();
5890
+
5122
5891
  // src/components/advanced-test-editor/advanced-test-editor.styles.ts
5123
5892
  var ADVANCED_TEST_EDITOR_STYLES = `
5124
5893
  :host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
@@ -5377,6 +6146,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
5377
6146
  isCreatingFolder = false;
5378
6147
  collapsedDirs = /* @__PURE__ */ new Set();
5379
6148
  sidebarWidth = 220;
6149
+ loginSetupConfig = null;
5380
6150
  hasPermission = false;
5381
6151
  needsReauth = false;
5382
6152
  _dirHandle = null;
@@ -5437,20 +6207,51 @@ var AdvancedTestEditorElement = class extends HTMLElement {
5437
6207
  const name = rawName.trim().replace(/\.cy\.ts$/, "");
5438
6208
  if (!name) return;
5439
6209
  const fileName = `${name}.cy.ts`;
5440
- const template = `describe('${escapeSingleQuotes(name)}', () => {
6210
+ const cfg = this.loginSetupConfig;
6211
+ if (cfg?.enabled && (cfg.beforeFn || cfg.beforeEachFn)) {
6212
+ const e2eName = this._e2eHandle.name ?? "e2e";
6213
+ const fromPath = `cypress/${e2eName}/${fileName}`;
6214
+ const importPath = buildLoginImportPath(fromPath, cfg.filePath);
6215
+ const { importLine, beforeBlock, beforeEachBlock } = buildLoginBlocks(
6216
+ importPath,
6217
+ cfg.beforeFn,
6218
+ cfg.beforeEachFn
6219
+ );
6220
+ const header = importLine ? `${importLine}
5441
6221
 
6222
+ ` : "";
6223
+ const inner = [beforeBlock, beforeEachBlock].filter(Boolean).join("");
6224
+ const template = `${header}describe('${escapeSingleQuotes(name)}', () => {
6225
+ ${inner}
5442
6226
  it('should ', () => {
5443
6227
 
5444
6228
  });
5445
6229
 
5446
6230
  });
5447
6231
  `;
5448
- try {
5449
- const fileHandle = await this._e2eHandle.getFileHandle(fileName, { create: true });
5450
- const writable = await fileHandle.createWritable();
5451
- await writable.write(template);
5452
- await writable.close();
5453
- } catch {
6232
+ try {
6233
+ const fileHandle = await this._e2eHandle.getFileHandle(fileName, { create: true });
6234
+ const writable = await fileHandle.createWritable();
6235
+ await writable.write(template);
6236
+ await writable.close();
6237
+ } catch {
6238
+ }
6239
+ } else {
6240
+ const template = `describe('${escapeSingleQuotes(name)}', () => {
6241
+
6242
+ it('should ', () => {
6243
+
6244
+ });
6245
+
6246
+ });
6247
+ `;
6248
+ try {
6249
+ const fileHandle = await this._e2eHandle.getFileHandle(fileName, { create: true });
6250
+ const writable = await fileHandle.createWritable();
6251
+ await writable.write(template);
6252
+ await writable.close();
6253
+ } catch {
6254
+ }
5454
6255
  }
5455
6256
  this.isCreatingFile = false;
5456
6257
  await this.getFoldersData();
@@ -5478,6 +6279,30 @@ var AdvancedTestEditorElement = class extends HTMLElement {
5478
6279
  const comment = this.testNotes ? this.transformationService.buildBlockComment(this.testNotes) + "\n" : "";
5479
6280
  content = this.transformationService.insertItBlock(content, comment + this.testItBlock);
5480
6281
  if (!content) return;
6282
+ const cfg = this.loginSetupConfig;
6283
+ if (cfg?.enabled && (cfg.beforeFn || cfg.beforeEachFn)) {
6284
+ const fns = [cfg.beforeFn, cfg.beforeEachFn].filter(Boolean);
6285
+ if (!hasLoginBlocks(content, fns)) {
6286
+ const e2eName = this._e2eHandle?.name ?? "e2e";
6287
+ const fileName = this.selectedFile?.name ?? "";
6288
+ const fromPath = `cypress/${e2eName}/${fileName}`;
6289
+ const importPath = buildLoginImportPath(fromPath, cfg.filePath);
6290
+ const { importLine, beforeBlock, beforeEachBlock } = buildLoginBlocks(importPath, cfg.beforeFn, cfg.beforeEachFn);
6291
+ const result = await import_sweetalert2.default.fire({
6292
+ title: this.t("CONFIG.LOGIN_SETUP_INJECT_TITLE"),
6293
+ html: `<div style="padding:10px 18px 14px;color:#8b949e;font-size:13px;line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif">${this.t("CONFIG.LOGIN_SETUP_INJECT_TEXT")}</div>`,
6294
+ icon: "question",
6295
+ showCancelButton: true,
6296
+ confirmButtonText: this.t("CONFIG.LOGIN_SETUP_INJECT_CONFIRM"),
6297
+ cancelButtonText: this.t("CONFIG.LOGIN_SETUP_CANCEL_BTN"),
6298
+ confirmButtonColor: "#1f6feb",
6299
+ width: 460
6300
+ });
6301
+ if (result.isConfirmed) {
6302
+ content = injectLoginBlocksIntoExisting(content, importLine, beforeBlock, beforeEachBlock);
6303
+ }
6304
+ }
6305
+ }
5481
6306
  const writable = await this.selectedFileHandle.createWritable();
5482
6307
  await writable.write(content);
5483
6308
  await writable.close();
@@ -5672,8 +6497,8 @@ var AdvancedTestEditorElement = class extends HTMLElement {
5672
6497
  });
5673
6498
  }
5674
6499
  };
5675
- if (!customElements.get("advanced-test-editor")) {
5676
- customElements.define("advanced-test-editor", AdvancedTestEditorElement);
6500
+ if (!customElements.get("lib-e2e-advanced-test-editor")) {
6501
+ customElements.define("lib-e2e-advanced-test-editor", AdvancedTestEditorElement);
5677
6502
  }
5678
6503
 
5679
6504
  // src/components/file-preview/file-preview.styles.ts
@@ -6039,8 +6864,8 @@ var FilePreviewElement = class extends HTMLElement {
6039
6864
  });
6040
6865
  }
6041
6866
  };
6042
- if (!customElements.get("file-preview")) {
6043
- customElements.define("file-preview", FilePreviewElement);
6867
+ if (!customElements.get("lib-e2e-file-preview")) {
6868
+ customElements.define("lib-e2e-file-preview", FilePreviewElement);
6044
6869
  }
6045
6870
 
6046
6871
  // src/components/help-panel/help-panel.styles.ts
@@ -6136,12 +6961,12 @@ var REFERENCE_SECTIONS = [
6136
6961
  { title: "HELP.SEC_PANELS", items: ["HELP.PANEL_TESTS", "HELP.PANEL_COMMANDS", "HELP.PANEL_CONFIG", "HELP.PANEL_FILES"] },
6137
6962
  { title: "HELP.SEC_SELECTOR", items: ["HELP.SELECTOR_1", "HELP.SELECTOR_2"] },
6138
6963
  { 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"] }
6964
+ { title: "HELP.SEC_ADVANCED", items: ["HELP.ADV_INVISIBLE", "HELP.ADV_RUNNER", "HELP.ADV_CROSSAPP", "HELP.ADV_DRAG", "HELP.ADV_LOGIN"] }
6140
6965
  ];
6141
6966
  var GUIDE_SECTIONS = [
6142
6967
  { 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"] }
6968
+ { title: "HELP.G_SEC_COVERED", items: ["HELP.G_COV_1", "HELP.G_COV_2", "HELP.G_COV_3", "HELP.G_COV_4", "HELP.G_COV_5", "HELP.G_COV_6", "HELP.G_COV_7"] },
6969
+ { 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"] }
6145
6970
  ];
6146
6971
  function sectionsHtml(sections, t) {
6147
6972
  return sections.map(
@@ -6188,12 +7013,12 @@ var HelpPanelElement = class extends HTMLElement {
6188
7013
  });
6189
7014
  }
6190
7015
  };
6191
- if (!customElements.get("help-panel")) {
6192
- customElements.define("help-panel", HelpPanelElement);
7016
+ if (!customElements.get("lib-e2e-help-panel")) {
7017
+ customElements.define("lib-e2e-help-panel", HelpPanelElement);
6193
7018
  }
6194
7019
 
6195
7020
  // src/components/lib-e2e-recorder/lib-e2e-recorder.ts
6196
- var import_sweetalert2 = __toESM(require("sweetalert2"), 1);
7021
+ var import_sweetalert22 = __toESM(require("sweetalert2"), 1);
6197
7022
 
6198
7023
  // src/components/lib-e2e-recorder/lib-e2e-recorder.styles.ts
6199
7024
  var DIRECTIONS = ["up-left", "up-right", "down-left", "down-right"];
@@ -6394,6 +7219,109 @@ function renderRecorderWidget(rec, paused, t) {
6394
7219
  <div class="rec-badge">${paused ? t("RECORDER.BADGE_PAUSED") : t("RECORDER.BADGE_REC")}</div>`;
6395
7220
  }
6396
7221
 
7222
+ // src/components/lib-e2e-recorder/assertion-builder.ts
7223
+ init_code_format_utils();
7224
+ var NO_VALUE_ASSERTIONS = /* @__PURE__ */ new Set(["be.visible", "not.exist", "be.disabled", "be.checked"]);
7225
+ function injectAssertionBuilder(container, t, onAddCommand) {
7226
+ container.insertAdjacentHTML("beforeend", `
7227
+ <div id="assertion-builder"
7228
+ style="padding:10px 12px;border-top:1px solid #21262d;background:#0d1117">
7229
+ <div style="font-size:10px;color:#484f58;text-transform:uppercase;letter-spacing:.8px;font-weight:600;margin-bottom:8px">
7230
+ ${t("RECORDER.ASSERTION_SECTION")}
7231
+ </div>
7232
+ <div style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end">
7233
+ <input id="assert-selector" type="text"
7234
+ placeholder="${t("RECORDER.ASSERT_SELECTOR_PH").replace(/"/g, "&quot;")}"
7235
+ style="flex:2;min-width:140px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
7236
+ border-radius:5px;color:#c9d1d9;font-size:11px;outline:none;
7237
+ font-family:'Cascadia Code','Fira Code',monospace"/>
7238
+ <select id="assert-type"
7239
+ style="padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;
7240
+ color:#c9d1d9;font-size:11px;outline:none;cursor:pointer;flex-shrink:0">
7241
+ <option value="be.visible">be.visible</option>
7242
+ <option value="not.exist">not.exist</option>
7243
+ <option value="be.disabled">be.disabled</option>
7244
+ <option value="be.checked">be.checked</option>
7245
+ <option value="contain.text">contain.text</option>
7246
+ <option value="have.value">have.value</option>
7247
+ <option value="have.length">have.length</option>
7248
+ <option value="have.class">have.class</option>
7249
+ <option value="have.attr">have.attr</option>
7250
+ </select>
7251
+ <input id="assert-value" type="text" placeholder="${t("RECORDER.ASSERT_VALUE_PH")}"
7252
+ style="flex:2;min-width:110px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
7253
+ border-radius:5px;color:#c9d1d9;font-size:11px;outline:none"/>
7254
+ <button id="btn-add-assertion"
7255
+ style="padding:5px 12px;border:none;border-radius:5px;cursor:pointer;
7256
+ font-size:11px;font-weight:500;background:#2f81f7;color:#fff;
7257
+ white-space:nowrap;flex-shrink:0">
7258
+ ${t("RECORDER.ASSERT_ADD_BTN")}
7259
+ </button>
7260
+ </div>
7261
+ </div>`);
7262
+ document.getElementById("btn-add-assertion")?.addEventListener("click", () => {
7263
+ const sel = document.getElementById("assert-selector").value.trim();
7264
+ const type = document.getElementById("assert-type").value;
7265
+ const val = document.getElementById("assert-value").value.trim();
7266
+ if (!sel) return;
7267
+ const s = escapeSingleQuotes(sel);
7268
+ const v = escapeSingleQuotes(val);
7269
+ const cmd = NO_VALUE_ASSERTIONS.has(type) || !val ? `cy.get('${s}').should('${type}')` : `cy.get('${s}').should('${type}', '${v}')`;
7270
+ onAddCommand(cmd);
7271
+ document.getElementById("assert-selector").value = "";
7272
+ document.getElementById("assert-value").value = "";
7273
+ });
7274
+ }
7275
+
7276
+ // src/components/lib-e2e-recorder/filesystem-setup.ts
7277
+ function mountFilesystemSetupContent(container, t, onSkip, onSelect) {
7278
+ container.innerHTML = `
7279
+ <div style="padding:16px 20px 20px;color:#8b949e;font-size:13px;line-height:1.7">
7280
+ <p>${t("RECORDER.FS_INTRO_HTML")}</p>
7281
+ <p style="margin-top:10px;margin-bottom:6px;font-size:11px;color:#8b949e">
7282
+ ${t("RECORDER.FS_STRUCTURE_HINT_HTML")}
7283
+ </p>
7284
+ <pre style="margin:0;padding:10px 14px;background:#0d1117;border:1px solid #21262d;
7285
+ border-radius:8px;font-size:11px;color:#c9d1d9;line-height:1.8;
7286
+ font-family:'Cascadia Code','Fira Code','Consolas',monospace">
7287
+ cypress/ <span style="color:#484f58">${t("RECORDER.FS_TREE_PICK_HINT")}</span>
7288
+ \u2514\u2500\u2500 e2e/ <span style="color:#484f58">${t("RECORDER.FS_TREE_READ_HINT")}</span>
7289
+ \u2514\u2500\u2500 *.cy.ts</pre>
7290
+ <p style="margin-top:8px;font-size:11px;color:#484f58">
7291
+ ${t("RECORDER.FS_PERMISSION_NOTE")}
7292
+ </p>
7293
+ <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:20px">
7294
+ <button id="fs-skip"
7295
+ style="padding:7px 16px;border:1px solid #30363d;border-radius:6px;cursor:pointer;
7296
+ font-size:12px;font-weight:500;background:transparent;color:#8b949e">
7297
+ ${t("RECORDER.FS_LATER_BTN")}
7298
+ </button>
7299
+ <button id="fs-select"
7300
+ style="padding:7px 16px;border:none;border-radius:6px;cursor:pointer;
7301
+ font-size:12px;font-weight:500;background:#2f81f7;color:#fff">
7302
+ ${t("RECORDER.FS_SELECT_BTN")}
7303
+ </button>
7304
+ </div>
7305
+ </div>`;
7306
+ document.getElementById("fs-skip")?.addEventListener("click", () => {
7307
+ void onSkip();
7308
+ });
7309
+ document.getElementById("fs-select")?.addEventListener("click", () => {
7310
+ void onSelect();
7311
+ });
7312
+ }
7313
+
7314
+ // src/utils/swal-mount.utils.ts
7315
+ function mountComponentInSwal(tagName, container, props, events) {
7316
+ const el = document.createElement(tagName);
7317
+ Object.assign(el, props);
7318
+ container.appendChild(el);
7319
+ for (const { name, handler } of events ?? []) {
7320
+ el.addEventListener(name, handler);
7321
+ }
7322
+ return el;
7323
+ }
7324
+
6397
7325
  // src/utils/fixture-convert.utils.ts
6398
7326
  function toFixtureInterceptors(interceptors, fixtures) {
6399
7327
  const fixtureNames = new Set(fixtures.map((f) => f.name));
@@ -6513,6 +7441,10 @@ var LibE2eRecorderElement = class extends HTMLElement {
6513
7441
  this.recording.destroy();
6514
7442
  this._needsRecordingRebuild = true;
6515
7443
  }
7444
+ // HttpMonitor reads extendedHttpCommands and fixtureMode synchronously inside
7445
+ // fetch/XHR interception handlers, so they must live in localStorage (sync).
7446
+ // IndexedDB is the persistent source of truth; this method mirrors it into
7447
+ // localStorage on every mount so the sync cache is always fresh.
6516
7448
  async initHttpConfig() {
6517
7449
  const config = await this.persistence.getConfig("extendedHttpCommands");
6518
7450
  if (config === null) {
@@ -6560,9 +7492,9 @@ var LibE2eRecorderElement = class extends HTMLElement {
6560
7492
  }
6561
7493
  showSelectorPicker(target) {
6562
7494
  Promise.resolve().then(() => (init_selector_picker(), selector_picker_exports)).then(() => {
6563
- const existing = document.querySelector("selector-picker");
7495
+ const existing = document.querySelector("lib-e2e-selector-picker");
6564
7496
  if (existing) existing.remove();
6565
- const picker = document.createElement("selector-picker");
7497
+ const picker = document.createElement("lib-e2e-selector-picker");
6566
7498
  picker.targetElement = target;
6567
7499
  picker.recording = this.recording;
6568
7500
  picker.translation = this.translation;
@@ -6624,7 +7556,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
6624
7556
  }
6625
7557
  async promptResumeOrDiscard(session) {
6626
7558
  const t = this.translation.translate.bind(this.translation);
6627
- const result = await import_sweetalert2.default.fire({
7559
+ const result = await import_sweetalert22.default.fire({
6628
7560
  title: t("RECORDER.SESSION_RESUME_TITLE"),
6629
7561
  html: `<div style="padding:8px 4px;color:#8b949e;font-size:13px;line-height:1.6"><p>${t("RECORDER.SESSION_RESUME_TEXT")}</p><p style="margin-top:6px;color:#c9d1d9"><b>${session.commands.length}</b> ${t("RECORDER.SESSION_RESUME_COUNT")}</p></div>`,
6630
7562
  showCancelButton: true,
@@ -6843,8 +7775,9 @@ var LibE2eRecorderElement = class extends HTMLElement {
6843
7775
  this.showFilesystemSetupDialog();
6844
7776
  }
6845
7777
  showFilesystemSetupDialog() {
6846
- import_sweetalert2.default.fire({
6847
- title: this.translation.translate("RECORDER.FS_TITLE"),
7778
+ const t = this.translation.translate.bind(this.translation);
7779
+ import_sweetalert22.default.fire({
7780
+ title: t("RECORDER.FS_TITLE"),
6848
7781
  html: '<div id="fs-setup-content"></div>',
6849
7782
  showCloseButton: true,
6850
7783
  showConfirmButton: false,
@@ -6853,277 +7786,171 @@ var LibE2eRecorderElement = class extends HTMLElement {
6853
7786
  setSwal2DataCyAttribute();
6854
7787
  const container = document.getElementById("fs-setup-content");
6855
7788
  if (!container) return;
6856
- container.innerHTML = `
6857
- <div style="padding:16px 20px 20px;color:#8b949e;font-size:13px;line-height:1.7">
6858
- <p>${this.translation.translate("RECORDER.FS_INTRO_HTML")}</p>
6859
- <p style="margin-top:10px;margin-bottom:6px;font-size:11px;color:#8b949e">
6860
- ${this.translation.translate("RECORDER.FS_STRUCTURE_HINT_HTML")}
6861
- </p>
6862
- <pre style="margin:0;padding:10px 14px;background:#0d1117;border:1px solid #21262d;
6863
- border-radius:8px;font-size:11px;color:#c9d1d9;line-height:1.8;
6864
- font-family:'Cascadia Code','Fira Code','Consolas',monospace">
6865
- cypress/ <span style="color:#484f58">${this.translation.translate("RECORDER.FS_TREE_PICK_HINT")}</span>
6866
- \u2514\u2500\u2500 e2e/ <span style="color:#484f58">${this.translation.translate("RECORDER.FS_TREE_READ_HINT")}</span>
6867
- \u2514\u2500\u2500 *.cy.ts</pre>
6868
- <p style="margin-top:8px;font-size:11px;color:#484f58">
6869
- ${this.translation.translate("RECORDER.FS_PERMISSION_NOTE")}
6870
- </p>
6871
- <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:20px">
6872
- <button id="fs-skip"
6873
- style="padding:7px 16px;border:1px solid #30363d;border-radius:6px;cursor:pointer;
6874
- font-size:12px;font-weight:500;background:transparent;color:#8b949e">
6875
- ${this.translation.translate("RECORDER.FS_LATER_BTN")}
6876
- </button>
6877
- <button id="fs-select"
6878
- style="padding:7px 16px;border:none;border-radius:6px;cursor:pointer;
6879
- font-size:12px;font-weight:500;background:#2f81f7;color:#fff">
6880
- ${this.translation.translate("RECORDER.FS_SELECT_BTN")}
6881
- </button>
6882
- </div>
6883
- </div>`;
6884
- document.getElementById("fs-skip")?.addEventListener("click", async () => {
6885
- await this.persistence.setConfigKey("allowReadWriteFiles", "false");
6886
- import_sweetalert2.default.close();
6887
- });
6888
- document.getElementById("fs-select")?.addEventListener("click", async () => {
6889
- try {
6890
- await this.persistence.requestDirectoryPermissions();
6891
- import_sweetalert2.default.close();
6892
- showToast(this.translation.translate("RECORDER.FS_SUCCESS_TOAST"));
6893
- } catch (e) {
6894
- if (e?.name !== "AbortError") {
6895
- showToast(this.translation.translate("RECORDER.FS_ERROR_TOAST"), false);
7789
+ mountFilesystemSetupContent(
7790
+ container,
7791
+ t,
7792
+ async () => {
7793
+ await this.persistence.setConfigKey("allowReadWriteFiles", "false");
7794
+ import_sweetalert22.default.close();
7795
+ },
7796
+ async () => {
7797
+ try {
7798
+ await this.persistence.requestDirectoryPermissions();
7799
+ import_sweetalert22.default.close();
7800
+ showToast(t("RECORDER.FS_SUCCESS_TOAST"));
7801
+ } catch (e) {
7802
+ if (e?.name !== "AbortError") {
7803
+ showToast(t("RECORDER.FS_ERROR_TOAST"), false);
7804
+ }
6896
7805
  }
6897
7806
  }
6898
- });
7807
+ );
6899
7808
  }
6900
7809
  });
6901
7810
  }
6902
7811
  // ── dialogs ──────────────────────────────────────────────────────────────
6903
7812
  showCommandsDialog() {
6904
- this.toggleModal("isCommandsDialogOpen", () => {
6905
- import_sweetalert2.default.fire({
7813
+ this.openSwalDialog(
7814
+ "isCommandsDialogOpen",
7815
+ "commands-modal-content",
7816
+ {
6906
7817
  title: this.translation.translate("MAIN_FRAME.DIALOG_COMMANDS"),
6907
7818
  html: '<div id="commands-modal-content" style="min-height:250px;padding:0"></div>',
6908
7819
  showCloseButton: true,
6909
- showConfirmButton: false,
6910
7820
  allowOutsideClick: false,
6911
7821
  backdrop: false,
6912
- width: 640,
6913
- color: "#e6edf3",
6914
- didOpen: () => {
6915
- makeSwalDraggable();
6916
- setSwal2DataCyAttribute();
7822
+ width: 640
7823
+ },
7824
+ (container) => {
7825
+ const child = mountComponentInSwal("lib-e2e-test-previsualizer", container, {
7826
+ translation: this.translation,
7827
+ commands: this.cypressCommands,
7828
+ interceptors: this.interceptors,
7829
+ editable: true
7830
+ }, [
7831
+ { name: "deletecommand", handler: (e) => this.recording.removeCommand(e.detail) },
7832
+ { name: "movecommand", handler: (e) => this.recording.moveCommand(e.detail.from, e.detail.to) },
7833
+ { name: "deleteinterceptor", handler: (e) => this.recording.removeInterceptor(e.detail) }
7834
+ ]);
7835
+ this._previsualizerRef = child;
7836
+ injectAssertionBuilder(container, this.translation.translate.bind(this.translation), (cmd) => this.recording.appendCommand(cmd));
7837
+ },
7838
+ {
7839
+ // Make the container click-through so the page underneath remains interactive
7840
+ onDidOpenPreamble: () => {
6917
7841
  const swalContainer = document.querySelector(".swal2-container");
6918
7842
  if (swalContainer) {
6919
7843
  swalContainer.style.pointerEvents = "none";
6920
7844
  const popup = swalContainer.querySelector(".swal2-popup");
6921
7845
  if (popup) popup.style.pointerEvents = "all";
6922
7846
  }
6923
- const container = document.getElementById("commands-modal-content");
6924
- if (!container) return;
6925
- const child = document.createElement("test-previsualizer");
6926
- child.translation = this.translation;
6927
- child.commands = this.cypressCommands;
6928
- child.interceptors = this.interceptors;
6929
- child.editable = true;
6930
- container.appendChild(child);
6931
- this._previsualizerRef = child;
6932
- child.addEventListener("deletecommand", (e) => {
6933
- this.recording.removeCommand(e.detail);
6934
- });
6935
- child.addEventListener("movecommand", (e) => {
6936
- this.recording.moveCommand(e.detail.from, e.detail.to);
6937
- });
6938
- child.addEventListener("deleteinterceptor", (e) => {
6939
- this.recording.removeInterceptor(e.detail);
6940
- });
6941
- const assertionHtml = `
6942
- <div id="assertion-builder"
6943
- style="padding:10px 12px;border-top:1px solid #21262d;background:#0d1117">
6944
- <div style="font-size:10px;color:#484f58;text-transform:uppercase;letter-spacing:.8px;font-weight:600;margin-bottom:8px">
6945
- ${this.translation.translate("RECORDER.ASSERTION_SECTION")}
6946
- </div>
6947
- <div style="display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end">
6948
- <input id="assert-selector" type="text"
6949
- placeholder="${this.translation.translate("RECORDER.ASSERT_SELECTOR_PH").replace(/"/g, "&quot;")}"
6950
- style="flex:2;min-width:140px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
6951
- border-radius:5px;color:#c9d1d9;font-size:11px;outline:none;
6952
- font-family:'Cascadia Code','Fira Code',monospace"/>
6953
- <select id="assert-type"
6954
- style="padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;
6955
- color:#c9d1d9;font-size:11px;outline:none;cursor:pointer;flex-shrink:0">
6956
- <option value="be.visible">be.visible</option>
6957
- <option value="not.exist">not.exist</option>
6958
- <option value="be.disabled">be.disabled</option>
6959
- <option value="be.checked">be.checked</option>
6960
- <option value="contain.text">contain.text</option>
6961
- <option value="have.value">have.value</option>
6962
- <option value="have.length">have.length</option>
6963
- <option value="have.class">have.class</option>
6964
- <option value="have.attr">have.attr</option>
6965
- </select>
6966
- <input id="assert-value" type="text" placeholder="${this.translation.translate("RECORDER.ASSERT_VALUE_PH")}"
6967
- style="flex:2;min-width:110px;padding:5px 8px;background:#161b22;border:1px solid #30363d;
6968
- border-radius:5px;color:#c9d1d9;font-size:11px;outline:none"/>
6969
- <button id="btn-add-assertion"
6970
- style="padding:5px 12px;border:none;border-radius:5px;cursor:pointer;
6971
- font-size:11px;font-weight:500;background:#2f81f7;color:#fff;
6972
- white-space:nowrap;flex-shrink:0">
6973
- ${this.translation.translate("RECORDER.ASSERT_ADD_BTN")}
6974
- </button>
6975
- </div>
6976
- </div>`;
6977
- container.insertAdjacentHTML("beforeend", assertionHtml);
6978
- const NO_VALUE_ASSERTIONS = /* @__PURE__ */ new Set(["be.visible", "not.exist", "be.disabled", "be.checked"]);
6979
- document.getElementById("btn-add-assertion")?.addEventListener("click", () => {
6980
- const sel = document.getElementById("assert-selector").value.trim();
6981
- const type = document.getElementById("assert-type").value;
6982
- const val = document.getElementById("assert-value").value.trim();
6983
- if (!sel) return;
6984
- const s = escapeSingleQuotes(sel);
6985
- const v = escapeSingleQuotes(val);
6986
- const cmd = NO_VALUE_ASSERTIONS.has(type) || !val ? `cy.get('${s}').should('${type}')` : `cy.get('${s}').should('${type}', '${v}')`;
6987
- this.recording.appendCommand(cmd);
6988
- document.getElementById("assert-selector").value = "";
6989
- document.getElementById("assert-value").value = "";
6990
- });
6991
7847
  },
6992
- willClose: () => {
6993
- this.isCommandsDialogOpen = false;
7848
+ onWillClose: () => {
6994
7849
  this._previsualizerRef = null;
6995
7850
  }
6996
- });
6997
- this.resizePopup();
6998
- });
7851
+ }
7852
+ );
6999
7853
  }
7000
7854
  showSavedTestsDialog() {
7001
- this.toggleModal("isSavedTestsDialogOpen", () => {
7002
- import_sweetalert2.default.fire({
7855
+ this.openSwalDialog(
7856
+ "isSavedTestsDialogOpen",
7857
+ "saved-tests-modal-content",
7858
+ {
7003
7859
  title: this.translation.translate("MAIN_FRAME.DIALOG_SAVED_TESTS"),
7004
7860
  html: '<div id="saved-tests-modal-content" style="min-height:250px;padding:0"></div>',
7005
7861
  showCloseButton: true,
7006
- showConfirmButton: false,
7007
- width: 680,
7008
- color: "#e6edf3",
7009
- didOpen: () => {
7010
- makeSwalDraggable();
7011
- setSwal2DataCyAttribute();
7012
- const container = document.getElementById("saved-tests-modal-content");
7013
- if (!container) return;
7014
- const child = document.createElement("test-editor");
7015
- child.persistence = this.persistence;
7016
- child.translation = this.translation;
7017
- child.issueTrackerConfig = this.issueTrackerConfig;
7018
- container.appendChild(child);
7019
- },
7020
- willClose: () => {
7021
- this.isSavedTestsDialogOpen = false;
7022
- }
7023
- });
7024
- this.resizePopup();
7025
- });
7862
+ width: 680
7863
+ },
7864
+ (container) => {
7865
+ mountComponentInSwal("lib-e2e-test-editor", container, {
7866
+ persistence: this.persistence,
7867
+ translation: this.translation,
7868
+ issueTrackerConfig: this.issueTrackerConfig
7869
+ });
7870
+ }
7871
+ );
7026
7872
  }
7027
7873
  showSaveTestDialog() {
7028
- this.toggleModal("isSaveTestDialogOpen", () => {
7029
- import_sweetalert2.default.fire({
7874
+ this.openSwalDialog(
7875
+ "isSaveTestDialogOpen",
7876
+ "save-test-modal-content",
7877
+ {
7030
7878
  title: this.translation.translate("MAIN_FRAME.DIALOG_SAVE"),
7031
7879
  html: '<div id="save-test-modal-content" style="padding:0"></div>',
7032
- showCloseButton: true,
7033
- showConfirmButton: false,
7880
+ showCloseButton: false,
7034
7881
  allowOutsideClick: false,
7035
- color: "#e6edf3",
7036
- didOpen: () => {
7037
- makeSwalDraggable();
7038
- setSwal2DataCyAttribute();
7039
- const container = document.getElementById("save-test-modal-content");
7040
- if (!container) return;
7041
- const child = document.createElement("save-test");
7042
- child.translation = this.translation;
7043
- child.issueTrackerConfig = this.issueTrackerConfig;
7044
- container.appendChild(child);
7045
- child.addEventListener("savetest", (e) => {
7882
+ allowEscapeKey: false
7883
+ },
7884
+ (container) => {
7885
+ mountComponentInSwal("lib-e2e-save-test", container, {
7886
+ translation: this.translation,
7887
+ issueTrackerConfig: this.issueTrackerConfig
7888
+ }, [
7889
+ { name: "savetest", handler: (e) => {
7046
7890
  const { description, notes, tags, ticketId } = e.detail ?? {};
7047
7891
  this.onSaveTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? "");
7048
- import_sweetalert2.default.close();
7049
- });
7050
- child.addEventListener("saveandexport", (e) => {
7892
+ import_sweetalert22.default.close();
7893
+ } },
7894
+ { name: "saveandexport", handler: (e) => {
7051
7895
  const { description, notes, tags, ticketId } = e.detail ?? {};
7052
7896
  this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? "");
7053
- import_sweetalert2.default.close();
7054
- });
7055
- },
7056
- willClose: () => {
7057
- this.isSaveTestDialogOpen = false;
7058
- }
7059
- });
7060
- });
7897
+ import_sweetalert22.default.close();
7898
+ } }
7899
+ ]);
7900
+ },
7901
+ { skipResize: true }
7902
+ );
7061
7903
  }
7062
7904
  showSettingsDialog() {
7063
- this.toggleModal("isSettingsDialogOpen", () => {
7064
- import_sweetalert2.default.fire({
7905
+ this.openSwalDialog(
7906
+ "isSettingsDialogOpen",
7907
+ "settings-modal-content",
7908
+ {
7065
7909
  title: this.translation.translate("MAIN_FRAME.SETTINGS"),
7066
7910
  html: '<div id="settings-modal-content" style="padding:0"></div>',
7067
7911
  showCloseButton: true,
7068
- showConfirmButton: false,
7069
- width: 520,
7070
- color: "#e6edf3",
7071
- didOpen: () => {
7072
- makeSwalDraggable();
7073
- setSwal2DataCyAttribute();
7074
- const container = document.getElementById("settings-modal-content");
7075
- if (!container) return;
7076
- const child = document.createElement("e2e-configuration");
7077
- child.persistence = this.persistence;
7078
- child.translation = this.translation;
7079
- container.appendChild(child);
7080
- child.addEventListener("smartselectorchange", (e) => {
7912
+ width: 520
7913
+ },
7914
+ (container) => {
7915
+ mountComponentInSwal("lib-e2e-configuration", container, {
7916
+ persistence: this.persistence,
7917
+ translation: this.translation
7918
+ }, [
7919
+ { name: "smartselectorchange", handler: (e) => {
7081
7920
  this.smartSelectorEnabled = e.detail;
7082
- });
7083
- child.addEventListener("starthiddenchange", (e) => {
7921
+ } },
7922
+ { name: "starthiddenchange", handler: (e) => {
7084
7923
  this.isVisible = !e.detail;
7085
7924
  this.style.display = this.isVisible ? "" : "none";
7086
- });
7087
- child.addEventListener("resetwidgetposition", () => this.resetWidgetPosition());
7088
- child.addEventListener("issuetrackerchange", (e) => {
7925
+ } },
7926
+ { name: "resetwidgetposition", handler: () => this.resetWidgetPosition() },
7927
+ { name: "issuetrackerchange", handler: (e) => {
7089
7928
  this.issueTrackerConfig = e.detail;
7090
- });
7091
- },
7092
- willClose: () => {
7093
- this.isSettingsDialogOpen = false;
7094
- }
7095
- });
7096
- this.resizePopup();
7097
- });
7929
+ } }
7930
+ ]);
7931
+ }
7932
+ );
7098
7933
  }
7099
7934
  showHelpDialog() {
7100
- this.toggleModal("isHelpDialogOpen", () => {
7101
- import_sweetalert2.default.fire({
7935
+ this.openSwalDialog(
7936
+ "isHelpDialogOpen",
7937
+ "help-modal-content",
7938
+ {
7102
7939
  title: this.translation.translate("HELP.TITLE"),
7103
7940
  html: '<div id="help-modal-content" style="padding:0"></div>',
7104
7941
  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
- });
7942
+ width: 560
7943
+ },
7944
+ (container) => {
7945
+ mountComponentInSwal("lib-e2e-help-panel", container, {
7946
+ translation: this.translation
7947
+ });
7948
+ }
7949
+ );
7123
7950
  }
7124
7951
  showAdvancedEditorDialog(testId) {
7125
7952
  this.toggleModal("isAdvancedEditorDialogOpen", () => {
7126
- import_sweetalert2.default.fire({
7953
+ import_sweetalert22.default.fire({
7127
7954
  title: this.translation.translate("MAIN_FRAME.SHOW_ADVANCED_EDITOR"),
7128
7955
  html: '<div id="advanced-editor-modal-content" style="flex:1;min-height:0;display:flex;flex-direction:column;padding:0"></div>',
7129
7956
  showCloseButton: true,
@@ -7134,42 +7961,33 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7134
7961
  didOpen: () => {
7135
7962
  makeSwalDraggable();
7136
7963
  setSwal2DataCyAttribute();
7137
- const popup = import_sweetalert2.default.getPopup();
7138
- if (popup) {
7139
- popup.style.height = "600px";
7140
- const htmlContainer = popup.querySelector(".swal2-html-container");
7141
- if (htmlContainer) {
7142
- htmlContainer.style.flex = "1";
7143
- htmlContainer.style.minHeight = "0";
7144
- htmlContainer.style.overflow = "hidden";
7145
- htmlContainer.style.padding = "0";
7146
- htmlContainer.style.margin = "0";
7147
- htmlContainer.style.display = "flex";
7148
- htmlContainer.style.flexDirection = "column";
7149
- }
7150
- }
7964
+ ensurePopupDimensions(import_sweetalert22.default.getPopup(), 600);
7151
7965
  const container = document.getElementById("advanced-editor-modal-content");
7152
7966
  if (!container) return;
7153
- const child = document.createElement("advanced-test-editor");
7154
- child.persistence = this.persistence;
7155
- child.translation = this.translation;
7156
- if (testId !== void 0) child.testId = testId;
7157
- container.appendChild(child);
7158
- child.addEventListener("selectorstrategychange", (e) => {
7159
- this.recording.selectorStrategy = e.detail;
7160
- });
7161
- child.addEventListener("closemodal", () => import_sweetalert2.default.close());
7162
- child.addEventListener("openfileeditor", (e) => {
7163
- import_sweetalert2.default.close();
7164
- setTimeout(() => this.showFileEditorDialog(
7165
- e.detail.handle,
7166
- e.detail.content,
7167
- e.detail.fileName,
7168
- e.detail.testId,
7169
- e.detail.itBlock,
7170
- e.detail.interceptorsBlock,
7171
- e.detail.notes
7172
- ), 150);
7967
+ const child = mountComponentInSwal("lib-e2e-advanced-test-editor", container, {
7968
+ persistence: this.persistence,
7969
+ translation: this.translation,
7970
+ ...testId !== void 0 ? { testId } : {}
7971
+ }, [
7972
+ { name: "selectorstrategychange", handler: (e) => {
7973
+ this.recording.selectorStrategy = e.detail;
7974
+ } },
7975
+ { name: "closemodal", handler: () => import_sweetalert22.default.close() },
7976
+ { name: "openfileeditor", handler: (e) => {
7977
+ import_sweetalert22.default.close();
7978
+ setTimeout(() => this.showFileEditorDialog(
7979
+ e.detail.handle,
7980
+ e.detail.content,
7981
+ e.detail.fileName,
7982
+ e.detail.testId,
7983
+ e.detail.itBlock,
7984
+ e.detail.interceptorsBlock,
7985
+ e.detail.notes
7986
+ ), 150);
7987
+ } }
7988
+ ]);
7989
+ void this.persistence.getLoginSetup().then((cfg) => {
7990
+ child.loginSetupConfig = cfg;
7173
7991
  });
7174
7992
  },
7175
7993
  willClose: () => {
@@ -7177,13 +7995,13 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7177
7995
  }
7178
7996
  });
7179
7997
  setTimeout(() => {
7180
- const popup = import_sweetalert2.default.getPopup();
7998
+ const popup = import_sweetalert22.default.getPopup();
7181
7999
  if (popup) makeModalResizable(popup, { minWidth: 400, minHeight: 0 });
7182
8000
  }, 0);
7183
8001
  });
7184
8002
  }
7185
8003
  showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "", notes = "") {
7186
- import_sweetalert2.default.fire({
8004
+ import_sweetalert22.default.fire({
7187
8005
  title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
7188
8006
  html: '<div id="file-editor-modal-content" style="flex:1;min-height:0;display:flex;flex-direction:column;padding:0"></div>',
7189
8007
  showCloseButton: false,
@@ -7194,47 +8012,35 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7194
8012
  didOpen: () => {
7195
8013
  makeSwalDraggable();
7196
8014
  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
- }
8015
+ ensurePopupDimensions(import_sweetalert22.default.getPopup(), 640);
7211
8016
  const container = document.getElementById("file-editor-modal-content");
7212
8017
  if (!container) return;
7213
- const child = document.createElement("file-preview");
7214
- child.translation = this.translation;
7215
- child.fileContent = content;
7216
- child.fileName = fileName;
7217
- child.closeLabel = this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR");
7218
- child.itBlock = itBlock;
7219
- child.interceptorsBlock = interceptorsBlock;
7220
- child.notes = notes;
7221
- container.appendChild(child);
7222
- child.addEventListener("close", () => {
7223
- import_sweetalert2.default.close();
7224
- setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
7225
- });
7226
- child.addEventListener("save", async (e) => {
7227
- try {
7228
- const writable = await handle.createWritable();
7229
- await writable.write(e.detail);
7230
- await writable.close();
7231
- showToast(this.translation.translate("RECORDER.FILE_SAVED_TOAST"));
7232
- } catch {
7233
- showToast(this.translation.translate("RECORDER.FILE_SAVE_ERROR_TOAST"), false);
7234
- }
7235
- import_sweetalert2.default.close();
7236
- setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
7237
- });
8018
+ mountComponentInSwal("lib-e2e-file-preview", container, {
8019
+ translation: this.translation,
8020
+ fileContent: content,
8021
+ fileName,
8022
+ closeLabel: this.translation.translate("FILE_PREVIEW.BACK_TO_EDITOR"),
8023
+ itBlock,
8024
+ interceptorsBlock,
8025
+ notes
8026
+ }, [
8027
+ { name: "close", handler: () => {
8028
+ import_sweetalert22.default.close();
8029
+ setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
8030
+ } },
8031
+ { name: "save", handler: async (e) => {
8032
+ try {
8033
+ const writable = await handle.createWritable();
8034
+ await writable.write(e.detail);
8035
+ await writable.close();
8036
+ showToast(this.translation.translate("RECORDER.FILE_SAVED_TOAST"));
8037
+ } catch {
8038
+ showToast(this.translation.translate("RECORDER.FILE_SAVE_ERROR_TOAST"), false);
8039
+ }
8040
+ import_sweetalert22.default.close();
8041
+ setTimeout(() => this.showAdvancedEditorDialog(testId), 150);
8042
+ } }
8043
+ ]);
7238
8044
  }
7239
8045
  });
7240
8046
  this.resizePopup();
@@ -7243,6 +8049,14 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7243
8049
  async onSaveTest(description, tags = [], notes = "", ticketId = "") {
7244
8050
  if (!description) return;
7245
8051
  await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes, ticketId || void 0);
8052
+ const uploadedFiles = this.recording.getUploadedFilesSnapshot();
8053
+ if (uploadedFiles.length > 0) {
8054
+ const filenames = uploadedFiles.map((f) => f.filename).join(", ");
8055
+ showToast(
8056
+ this.translation.translate("RECORDER.FILE_UPLOAD_MANUAL_TOAST").replace("{files}", filenames),
8057
+ false
8058
+ );
8059
+ }
7246
8060
  this.recording.clearCommands();
7247
8061
  this.clearRecordingHistory();
7248
8062
  this.cypressCommands = [];
@@ -7264,6 +8078,29 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7264
8078
  showToast(this.translation.translate("RECORDER.FIXTURES_NO_FOLDER_TOAST"), false);
7265
8079
  }
7266
8080
  }
8081
+ const uploadedFiles = this.recording.getUploadedFilesSnapshot();
8082
+ if (uploadedFiles.length > 0) {
8083
+ const filenames = uploadedFiles.map((f) => f.filename).join(", ");
8084
+ const hasAccess = await this.persistence.hasDirectoryAccess();
8085
+ if (hasAccess) {
8086
+ try {
8087
+ for (const uf of uploadedFiles) {
8088
+ await this.persistence.writeUploadedFile(uf.filename, uf.bytes);
8089
+ }
8090
+ showToast(this.translation.translate("RECORDER.FILE_UPLOAD_COPIED_TOAST").replace("{files}", filenames));
8091
+ } catch {
8092
+ showToast(
8093
+ this.translation.translate("RECORDER.FILE_UPLOAD_COPY_ERROR_TOAST").replace("{files}", filenames),
8094
+ false
8095
+ );
8096
+ }
8097
+ } else {
8098
+ showToast(
8099
+ this.translation.translate("RECORDER.FILE_UPLOAD_MANUAL_TOAST").replace("{files}", filenames),
8100
+ false
8101
+ );
8102
+ }
8103
+ }
7267
8104
  const id = await this.persistence.insertTest(description, finalCommands, finalInterceptors, tags, notes, ticketId || void 0);
7268
8105
  this.recording.clearCommands();
7269
8106
  this.clearRecordingHistory();
@@ -7274,7 +8111,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7274
8111
  // ── helpers ───────────────────────────────────────────────────────────────
7275
8112
  toggleModal(flag, openFn) {
7276
8113
  if (this[flag]) {
7277
- import_sweetalert2.default.close();
8114
+ import_sweetalert22.default.close();
7278
8115
  this[flag] = false;
7279
8116
  return;
7280
8117
  }
@@ -7283,10 +8120,32 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7283
8120
  }
7284
8121
  resizePopup() {
7285
8122
  setTimeout(() => {
7286
- const popup = import_sweetalert2.default.getPopup();
8123
+ const popup = import_sweetalert22.default.getPopup();
7287
8124
  if (popup) makeModalResizable(popup, { minWidth: 400, minHeight: 200 });
7288
8125
  }, 0);
7289
8126
  }
8127
+ openSwalDialog(flag, contentId, swalOptions, onMount, extra) {
8128
+ this.toggleModal(flag, () => {
8129
+ import_sweetalert22.default.fire({
8130
+ showConfirmButton: false,
8131
+ color: "#e6edf3",
8132
+ ...swalOptions,
8133
+ didOpen: () => {
8134
+ makeSwalDraggable();
8135
+ setSwal2DataCyAttribute();
8136
+ extra?.onDidOpenPreamble?.();
8137
+ const container = document.getElementById(contentId);
8138
+ if (!container) return;
8139
+ onMount(container);
8140
+ },
8141
+ willClose: () => {
8142
+ this[flag] = false;
8143
+ extra?.onWillClose?.();
8144
+ }
8145
+ });
8146
+ if (!extra?.skipResize) this.resizePopup();
8147
+ });
8148
+ }
7290
8149
  render() {
7291
8150
  const rec = this.isRecording;
7292
8151
  const paused = this.isPaused;
@@ -7315,7 +8174,7 @@ if (!customElements.get("lib-e2e-recorder")) {
7315
8174
  }
7316
8175
 
7317
8176
  // src/index.ts
7318
- var VERSION = "0.9.0";
8177
+ var VERSION = "0.10.0";
7319
8178
  // Annotate the CommonJS export names for ESM import in node:
7320
8179
  0 && (module.exports = {
7321
8180
  ACTIVE_SESSION_BREADCRUMB_KEY,
@@ -7340,18 +8199,19 @@ var VERSION = "0.9.0";
7340
8199
  SUPPORTED_LANGS,
7341
8200
  SaveTestElement,
7342
8201
  SelectorPickerElement,
7343
- Subject,
7344
8202
  TOGGLE_MARGIN,
7345
8203
  TestEditorElement,
7346
8204
  TestPrevisualizerElement,
7347
8205
  TransformationService,
7348
8206
  TranslationService,
7349
8207
  VERSION,
8208
+ _resetHttpMonitorState,
7350
8209
  advancedTestTransformationService,
7351
8210
  boxOffsetForDirection,
7352
8211
  boxTopLeftFor,
7353
8212
  clampTogglePosition,
7354
8213
  defaultTogglePosition,
8214
+ ensurePopupDimensions,
7355
8215
  generateAlias,
7356
8216
  inferAssertionCommand,
7357
8217
  injectStyles,
@@ -7361,7 +8221,6 @@ var VERSION = "0.9.0";
7361
8221
  makeModalResizable,
7362
8222
  makeSwalDraggable,
7363
8223
  makeSwalDraggableByContentId,
7364
- persistenceService,
7365
8224
  resolveExpandDirection,
7366
8225
  selectTestsForExport,
7367
8226
  setSwal2DataCyAttribute,