lib-e2e-cypress-for-dummys-ts 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -1
- package/dist/index.cjs +1611 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +212 -5
- package/dist/index.d.ts +212 -5
- package/dist/index.js +1594 -150
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/dist/index.cjs
CHANGED
|
@@ -463,14 +463,20 @@ __export(index_exports, {
|
|
|
463
463
|
ACTIVE_SESSION_BREADCRUMB_KEY: () => ACTIVE_SESSION_BREADCRUMB_KEY,
|
|
464
464
|
AdvancedTestEditorElement: () => AdvancedTestEditorElement,
|
|
465
465
|
AdvancedTestTransformationService: () => AdvancedTestTransformationService,
|
|
466
|
+
CHAIN_METHODS: () => CHAIN_METHODS,
|
|
467
|
+
CYPRESS_COMPLETIONS: () => CYPRESS_COMPLETIONS,
|
|
468
|
+
CY_METHODS: () => CY_METHODS,
|
|
466
469
|
ConfigurationElement: () => ConfigurationElement,
|
|
467
470
|
DB_SCHEMA: () => DB_SCHEMA,
|
|
468
471
|
DB_STORE_NAMES: () => DB_STORE_NAMES,
|
|
472
|
+
DEFAULT_ISSUE_TRACKER_PROVIDER_ID: () => DEFAULT_ISSUE_TRACKER_PROVIDER_ID,
|
|
469
473
|
DEFAULT_RESUME_TTL_MINUTES: () => DEFAULT_RESUME_TTL_MINUTES,
|
|
470
474
|
DRAG_THRESHOLD: () => DRAG_THRESHOLD,
|
|
471
475
|
FilePreviewElement: () => FilePreviewElement,
|
|
476
|
+
HelpPanelElement: () => HelpPanelElement,
|
|
472
477
|
HttpMonitor: () => HttpMonitor,
|
|
473
478
|
INPUT_TYPES: () => INPUT_TYPES,
|
|
479
|
+
ISSUE_TRACKER_PROVIDERS: () => ISSUE_TRACKER_PROVIDERS,
|
|
474
480
|
LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES: () => LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
|
|
475
481
|
LOCALE_BY_LANG: () => LOCALE_BY_LANG,
|
|
476
482
|
LibE2eRecorderElement: () => LibE2eRecorderElement,
|
|
@@ -483,6 +489,7 @@ __export(index_exports, {
|
|
|
483
489
|
SelectorPickerElement: () => SelectorPickerElement,
|
|
484
490
|
Subject: () => Subject,
|
|
485
491
|
TOGGLE_MARGIN: () => TOGGLE_MARGIN,
|
|
492
|
+
TOP_LEVEL: () => TOP_LEVEL,
|
|
486
493
|
TestEditorElement: () => TestEditorElement,
|
|
487
494
|
TestPrevisualizerElement: () => TestPrevisualizerElement,
|
|
488
495
|
TransformationService: () => TransformationService,
|
|
@@ -491,9 +498,16 @@ __export(index_exports, {
|
|
|
491
498
|
advancedTestTransformationService: () => advancedTestTransformationService,
|
|
492
499
|
boxOffsetForDirection: () => boxOffsetForDirection,
|
|
493
500
|
boxTopLeftFor: () => boxTopLeftFor,
|
|
501
|
+
buildTicketComment: () => buildTicketComment,
|
|
502
|
+
buildTicketUrl: () => buildTicketUrl,
|
|
494
503
|
clampTogglePosition: () => clampTogglePosition,
|
|
504
|
+
createCodeEditor: () => createCodeEditor,
|
|
495
505
|
defaultTogglePosition: () => defaultTogglePosition,
|
|
506
|
+
filterCompletions: () => filterCompletions,
|
|
507
|
+
filterCypressCompletions: () => filterCypressCompletions,
|
|
508
|
+
findIssueTrackerProvider: () => findIssueTrackerProvider,
|
|
496
509
|
generateAlias: () => generateAlias,
|
|
510
|
+
inferAssertionCommand: () => inferAssertionCommand,
|
|
497
511
|
injectStyles: () => injectStyles,
|
|
498
512
|
isLang: () => isLang,
|
|
499
513
|
isLocalHost: () => isLocalHost,
|
|
@@ -503,6 +517,7 @@ __export(index_exports, {
|
|
|
503
517
|
makeSwalDraggableByContentId: () => makeSwalDraggableByContentId,
|
|
504
518
|
persistenceService: () => persistenceService,
|
|
505
519
|
resolveExpandDirection: () => resolveExpandDirection,
|
|
520
|
+
resolveTicketUrl: () => resolveTicketUrl,
|
|
506
521
|
selectTestsForExport: () => selectTestsForExport,
|
|
507
522
|
setSwal2DataCyAttribute: () => setSwal2DataCyAttribute,
|
|
508
523
|
showToast: () => showToast,
|
|
@@ -601,6 +616,64 @@ var ACTIVE_SESSION_BREADCRUMB_KEY = "e2e-active-session";
|
|
|
601
616
|
var RESUME_TTL_CONFIG_KEY = "resumeRecencyTtlMinutes";
|
|
602
617
|
var DEFAULT_RESUME_TTL_MINUTES = 30;
|
|
603
618
|
|
|
619
|
+
// src/models/issue-tracker.model.ts
|
|
620
|
+
var ISSUE_TRACKER_PROVIDERS = [
|
|
621
|
+
{
|
|
622
|
+
id: "jira",
|
|
623
|
+
labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_JIRA",
|
|
624
|
+
urlTemplate: "{baseUrl}/browse/{id}",
|
|
625
|
+
fields: [
|
|
626
|
+
{ key: "baseUrl", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_BASEURL", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_JIRA_BASEURL" }
|
|
627
|
+
],
|
|
628
|
+
idPattern: "^[A-Za-z][A-Za-z0-9_]+-\\d+$",
|
|
629
|
+
idPlaceholderKey: "SAVE_TEST.TICKET_PH_JIRA"
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
id: "azure-devops",
|
|
633
|
+
labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_AZURE",
|
|
634
|
+
urlTemplate: "https://dev.azure.com/{org}/{project}/_workitems/edit/{id}",
|
|
635
|
+
fields: [
|
|
636
|
+
{ key: "org", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_ORG", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_AZURE_ORG" },
|
|
637
|
+
{ key: "project", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_PROJECT", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_AZURE_PROJECT" }
|
|
638
|
+
],
|
|
639
|
+
idPattern: "^\\d+$",
|
|
640
|
+
idPlaceholderKey: "SAVE_TEST.TICKET_PH_AZURE"
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
id: "github",
|
|
644
|
+
labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_GITHUB",
|
|
645
|
+
urlTemplate: "https://github.com/{owner}/{repo}/issues/{id}",
|
|
646
|
+
fields: [
|
|
647
|
+
{ key: "owner", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_OWNER", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_GITHUB_OWNER" },
|
|
648
|
+
{ key: "repo", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_REPO", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_GITHUB_REPO" }
|
|
649
|
+
],
|
|
650
|
+
idPattern: "^\\d+$",
|
|
651
|
+
idPlaceholderKey: "SAVE_TEST.TICKET_PH_GITHUB"
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
id: "trello",
|
|
655
|
+
labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_TRELLO",
|
|
656
|
+
urlTemplate: "https://trello.com/c/{id}",
|
|
657
|
+
fields: [],
|
|
658
|
+
idPattern: "^[A-Za-z0-9]+$",
|
|
659
|
+
idPlaceholderKey: "SAVE_TEST.TICKET_PH_TRELLO"
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
id: "custom",
|
|
663
|
+
labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_CUSTOM",
|
|
664
|
+
urlTemplate: "{template}",
|
|
665
|
+
fields: [
|
|
666
|
+
{ key: "template", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_TEMPLATE", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_CUSTOM_TEMPLATE" }
|
|
667
|
+
],
|
|
668
|
+
idPattern: ".*",
|
|
669
|
+
idPlaceholderKey: "SAVE_TEST.TICKET_PH_CUSTOM"
|
|
670
|
+
}
|
|
671
|
+
];
|
|
672
|
+
var DEFAULT_ISSUE_TRACKER_PROVIDER_ID = "custom";
|
|
673
|
+
function findIssueTrackerProvider(id) {
|
|
674
|
+
return ISSUE_TRACKER_PROVIDERS.find((p) => p.id === id);
|
|
675
|
+
}
|
|
676
|
+
|
|
604
677
|
// src/utils/subject.ts
|
|
605
678
|
var Subject = class {
|
|
606
679
|
_value;
|
|
@@ -645,7 +718,14 @@ var I18N_ES = {
|
|
|
645
718
|
SAVE_AND_EDIT: "\u{1F4DD} Guardar y editar",
|
|
646
719
|
REMOVE_TAG_TITLE: "Quitar",
|
|
647
720
|
NOTES_LABEL: "Descripci\xF3n / Notas (opcional):",
|
|
648
|
-
NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc."
|
|
721
|
+
NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc.",
|
|
722
|
+
TICKET_LABEL: "Ticket (opcional):",
|
|
723
|
+
TICKET_PH_JIRA: "ej. PROJ-123",
|
|
724
|
+
TICKET_PH_AZURE: "ej. 1234",
|
|
725
|
+
TICKET_PH_GITHUB: "ej. 42",
|
|
726
|
+
TICKET_PH_TRELLO: "ej. aBc123",
|
|
727
|
+
TICKET_PH_CUSTOM: "id del ticket",
|
|
728
|
+
TICKET_INVALID: "El id del ticket no coincide con el formato esperado del gestor seleccionado."
|
|
649
729
|
},
|
|
650
730
|
TEST_EDITOR: {
|
|
651
731
|
NO_TAGS: "Sin etiquetas",
|
|
@@ -659,7 +739,9 @@ var I18N_ES = {
|
|
|
659
739
|
COPY_ICPS_BTN: "\u{1F4CB} Copiar interceptores",
|
|
660
740
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
661
741
|
NO_TESTS: "No hay tests guardados",
|
|
662
|
-
DELETE_TITLE: "Eliminar"
|
|
742
|
+
DELETE_TITLE: "Eliminar",
|
|
743
|
+
GROUP_BY_TICKET: "\u{1F3AB} Agrupar por ticket",
|
|
744
|
+
NO_TICKET_GROUP: "Sin ticket"
|
|
663
745
|
},
|
|
664
746
|
CONFIG: {
|
|
665
747
|
LANG_SECTION: "\u{1F310} Idioma",
|
|
@@ -667,6 +749,9 @@ var I18N_ES = {
|
|
|
667
749
|
HTTP_SECTION: "\u26A1 HTTP Avanzado",
|
|
668
750
|
HTTP_TITLE: "Validaciones de body",
|
|
669
751
|
HTTP_SUB: "GET \u2192 response \xB7 POST/PUT \u2192 request",
|
|
752
|
+
FIXTURE_SECTION: "\u{1F9EA} Fixtures HTTP",
|
|
753
|
+
FIXTURE_TITLE: "Grabar respuestas como fixtures",
|
|
754
|
+
FIXTURE_SUB: "Las GET se guardan en cypress/fixtures y se stubean con cy.intercept({ fixture }).",
|
|
670
755
|
SELECTOR_SECTION: "\u{1F3AF} Estrategia de selector",
|
|
671
756
|
SELECTOR_LABEL: "Atributo preferido para cy.get()",
|
|
672
757
|
SELECTOR_OPT_DATACY: "data-cy (por defecto)",
|
|
@@ -714,7 +799,27 @@ var I18N_ES = {
|
|
|
714
799
|
WIDGET_POSITION_SECTION: "\u{1F9F2} Posici\xF3n del widget",
|
|
715
800
|
WIDGET_POSITION_HINT: "Arrastra el bot\xF3n de grabar para moverlo donde no estorbe. Se recuerda su posici\xF3n.",
|
|
716
801
|
WIDGET_POSITION_RESET_BTN: "\u21BA Restablecer posici\xF3n",
|
|
717
|
-
WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida"
|
|
802
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida",
|
|
803
|
+
ISSUE_TRACKER_SECTION: "\u{1F3AB} Gestor de incidencias",
|
|
804
|
+
ISSUE_TRACKER_LABEL: "Gestor (solo enlace, sin sincronizaci\xF3n)",
|
|
805
|
+
ISSUE_TRACKER_DISCLAIMER: "Solo genera enlaces \u2014 nunca se conecta ni sincroniza con la plataforma.",
|
|
806
|
+
ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
|
|
807
|
+
ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
|
|
808
|
+
ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
|
|
809
|
+
ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
|
|
810
|
+
ISSUE_TRACKER_PROVIDER_CUSTOM: "Personalizado",
|
|
811
|
+
ISSUE_TRACKER_FIELD_BASEURL: "URL base",
|
|
812
|
+
ISSUE_TRACKER_FIELD_ORG: "Organizaci\xF3n",
|
|
813
|
+
ISSUE_TRACKER_FIELD_PROJECT: "Proyecto",
|
|
814
|
+
ISSUE_TRACKER_FIELD_OWNER: "Propietario",
|
|
815
|
+
ISSUE_TRACKER_FIELD_REPO: "Repositorio",
|
|
816
|
+
ISSUE_TRACKER_FIELD_TEMPLATE: "Plantilla de URL (debe contener {id})",
|
|
817
|
+
ISSUE_TRACKER_PH_JIRA_BASEURL: "https://tu-org.atlassian.net",
|
|
818
|
+
ISSUE_TRACKER_PH_AZURE_ORG: "organizaci\xF3n",
|
|
819
|
+
ISSUE_TRACKER_PH_AZURE_PROJECT: "proyecto",
|
|
820
|
+
ISSUE_TRACKER_PH_GITHUB_OWNER: "propietario",
|
|
821
|
+
ISSUE_TRACKER_PH_GITHUB_REPO: "repositorio",
|
|
822
|
+
ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
|
|
718
823
|
},
|
|
719
824
|
SELECTOR_PICKER: {
|
|
720
825
|
TITLE: "Selecciona un elemento del DOM",
|
|
@@ -803,6 +908,8 @@ var I18N_ES = {
|
|
|
803
908
|
FILE_EDITOR_TITLE: "\u270F\uFE0F Editor de archivo",
|
|
804
909
|
FILE_SAVED_TOAST: "\u2713 Archivo guardado",
|
|
805
910
|
FILE_SAVE_ERROR_TOAST: "Error al guardar el archivo",
|
|
911
|
+
FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures escritos en cypress/fixtures",
|
|
912
|
+
FIXTURES_NO_FOLDER_TOAST: "No se pudieron escribir los fixtures (configura la carpeta Cypress)",
|
|
806
913
|
BTN_CONFIG: "Config",
|
|
807
914
|
BTN_FILES: "Ficheros",
|
|
808
915
|
BTN_COMMANDS: "Comandos",
|
|
@@ -817,7 +924,76 @@ var I18N_ES = {
|
|
|
817
924
|
SESSION_RESUME_TEXT: "Hay una grabaci\xF3n activa sin terminar.",
|
|
818
925
|
SESSION_RESUME_COUNT: "comandos capturados",
|
|
819
926
|
SESSION_CONTINUE_BTN: "Continuar grabando",
|
|
820
|
-
SESSION_DISCARD_BTN: "Descartar"
|
|
927
|
+
SESSION_DISCARD_BTN: "Descartar",
|
|
928
|
+
BTN_HELP: "Ayuda"
|
|
929
|
+
},
|
|
930
|
+
HELP: {
|
|
931
|
+
TITLE: "\u2753 Ayuda \u2014 Gu\xEDa r\xE1pida",
|
|
932
|
+
INTRO: "Todo lo que puede hacer el grabador.",
|
|
933
|
+
SEC_RECORDING: "\u23FA Grabar",
|
|
934
|
+
RECORDING_1: "Pulsa \u23FA (o Ctrl+R) para empezar; \u23F9 para parar y guardar.",
|
|
935
|
+
RECORDING_2: "Pausa / reanuda con \u23F8 (o Ctrl+P).",
|
|
936
|
+
SEC_SHORTCUTS: "\u2328\uFE0F Atajos de teclado",
|
|
937
|
+
SC_RECORD: "Ctrl+R \u2014 Grabar / detener",
|
|
938
|
+
SC_PAUSE: "Ctrl+P \u2014 Pausar / reanudar",
|
|
939
|
+
SC_TESTS: "Ctrl+1 \u2014 Panel de tests guardados",
|
|
940
|
+
SC_COMMANDS: "Ctrl+2 \u2014 Panel de comandos",
|
|
941
|
+
SC_CONFIG: "Ctrl+3 \u2014 Configuraci\xF3n",
|
|
942
|
+
SC_VISIBILITY: "Ctrl+Shift+E \u2014 Mostrar / ocultar el widget",
|
|
943
|
+
SC_HELP: "Ctrl+Shift+H \u2014 Esta ayuda",
|
|
944
|
+
SEC_CAPTURED: "\u{1F3AF} Qu\xE9 se graba",
|
|
945
|
+
CAP_CLICK: "Click \u2192 .click()",
|
|
946
|
+
CAP_DBLCLICK: "Doble-click \u2192 .dblclick()",
|
|
947
|
+
CAP_RIGHTCLICK: "Click derecho \u2192 .rightclick()",
|
|
948
|
+
CAP_TYPE: "Escribir en un campo \u2192 .clear().type(...)",
|
|
949
|
+
CAP_CHECK: "Checkbox / radio \u2192 .check() / .uncheck()",
|
|
950
|
+
CAP_SELECT: "<select> \u2192 .select(...)",
|
|
951
|
+
CAP_KEYS: 'Enter / Escape \u2192 .type("{enter}") / ("{esc}")',
|
|
952
|
+
CAP_ROUTE: "Cambio de ruta (SPA) \u2192 cy.url().should(...)",
|
|
953
|
+
CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
|
|
954
|
+
SEC_ASSERT: "\u2705 Aserciones",
|
|
955
|
+
ASSERT_1: "Alt+click sobre un elemento para verificarlo sin ejecutar su acci\xF3n (texto, valor, checked o visible).",
|
|
956
|
+
ASSERT_2: "O usa el constructor de aserciones en el panel de Comandos.",
|
|
957
|
+
SEC_PANELS: "\u{1F5C2} Paneles",
|
|
958
|
+
PANEL_TESTS: "\u{1F4CB} Tests \u2014 guardados: filtrar por tag, copiar, combinar, exportar.",
|
|
959
|
+
PANEL_COMMANDS: "\u2328\uFE0F Comandos \u2014 de la grabaci\xF3n actual: reordenar, borrar, a\xF1adir aserciones.",
|
|
960
|
+
PANEL_CONFIG: "\u2699\uFE0F Config \u2014 idioma, selector, HTTP, carpeta Cypress, datos, visibilidad, posici\xF3n.",
|
|
961
|
+
PANEL_FILES: "\u{1F4C1} Ficheros \u2014 inserta tests en tus .cy.ts y lanza el runner.",
|
|
962
|
+
SEC_SELECTOR: "\u{1F50E} Selectores",
|
|
963
|
+
SELECTOR_1: "Elige el atributo preferido (data-cy, data-testid, aria-label, id) en Config.",
|
|
964
|
+
SELECTOR_2: "Si un click no tiene selector fiable, aparece un picker para elegir el mejor ancestro.",
|
|
965
|
+
SEC_DATA: "\u{1F4BE} Datos",
|
|
966
|
+
DATA_1: "Exporta tests a JSON (todo / selecci\xF3n / por tags).",
|
|
967
|
+
DATA_2: "Importa un JSON: se fusiona con lo existente (no se borra nada).",
|
|
968
|
+
SEC_ADVANCED: "\u{1F680} Avanzado",
|
|
969
|
+
ADV_INVISIBLE: "Modo invisible: start-hidden + Ctrl+Shift+E para grabar en producci\xF3n sin molestar.",
|
|
970
|
+
ADV_RUNNER: "Runner local: npx lib-e2e-cypress-runner para lanzar un spec headless desde el editor.",
|
|
971
|
+
ADV_CROSSAPP: "Cross-app: la grabaci\xF3n sobrevive al cambiar de micro-frontend (mismo origen).",
|
|
972
|
+
ADV_DRAG: "Arrastra el bot\xF3n \u23FA para mover el widget; recuerda su posici\xF3n.",
|
|
973
|
+
CLOSE: "Cerrar",
|
|
974
|
+
TAB_REFERENCE: "Referencia r\xE1pida",
|
|
975
|
+
TAB_GUIDE: "Gu\xEDa de uso",
|
|
976
|
+
GUIDE_INTRO: "C\xF3mo sacarle partido al grabador, qu\xE9 cubre y qu\xE9 no.",
|
|
977
|
+
G_SEC_WORKFLOW: "\u{1F9ED} Flujo de trabajo",
|
|
978
|
+
G_WF_1: "1. Pulsa \u23FA (o Ctrl+R) para empezar a grabar.",
|
|
979
|
+
G_WF_2: "2. Usa tu app como un usuario: clicks, formularios, navegaci\xF3n. Cada acci\xF3n se convierte en un comando Cypress.",
|
|
980
|
+
G_WF_3: "3. A\xF1ade verificaciones con Alt+click sobre lo que quieras comprobar.",
|
|
981
|
+
G_WF_4: "4. Pulsa \u23F9 para parar; ponle nombre y tags, y gu\xE1rdalo.",
|
|
982
|
+
G_WF_5: "5. En \u{1F4C1} Ficheros, inserta el test en un .cy.ts o l\xE1nzalo con el runner local.",
|
|
983
|
+
G_SEC_COVERED: "\u2705 Qu\xE9 cubre",
|
|
984
|
+
G_COV_1: "Clicks, doble-click, click derecho, inputs de texto, checkbox/radio, select y teclas Enter/Escape.",
|
|
985
|
+
G_COV_2: "Navegaci\xF3n SPA (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
|
|
986
|
+
G_COV_3: "Llamadas fetch/XHR (GET/POST/PUT) \u2192 cy.intercept + cy.wait, con validaci\xF3n de body opcional.",
|
|
987
|
+
G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
|
|
988
|
+
G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
|
|
989
|
+
G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
|
|
990
|
+
G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
|
|
991
|
+
G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
|
|
992
|
+
G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
|
|
993
|
+
G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
|
|
994
|
+
G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
|
|
995
|
+
G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
|
|
996
|
+
G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
|
|
821
997
|
}
|
|
822
998
|
};
|
|
823
999
|
|
|
@@ -845,7 +1021,14 @@ var I18N_EN = {
|
|
|
845
1021
|
SAVE_AND_EDIT: "\u{1F4DD} Save and edit",
|
|
846
1022
|
REMOVE_TAG_TITLE: "Remove",
|
|
847
1023
|
NOTES_LABEL: "Description / Notes (optional):",
|
|
848
|
-
NOTES_PLACEHOLDER: "What this test validates, preconditions, etc."
|
|
1024
|
+
NOTES_PLACEHOLDER: "What this test validates, preconditions, etc.",
|
|
1025
|
+
TICKET_LABEL: "Ticket (optional):",
|
|
1026
|
+
TICKET_PH_JIRA: "e.g. PROJ-123",
|
|
1027
|
+
TICKET_PH_AZURE: "e.g. 1234",
|
|
1028
|
+
TICKET_PH_GITHUB: "e.g. 42",
|
|
1029
|
+
TICKET_PH_TRELLO: "e.g. aBc123",
|
|
1030
|
+
TICKET_PH_CUSTOM: "ticket id",
|
|
1031
|
+
TICKET_INVALID: "The ticket id does not match the expected format for the selected tracker."
|
|
849
1032
|
},
|
|
850
1033
|
TEST_EDITOR: {
|
|
851
1034
|
NO_TAGS: "No tags",
|
|
@@ -859,7 +1042,9 @@ var I18N_EN = {
|
|
|
859
1042
|
COPY_ICPS_BTN: "\u{1F4CB} Copy interceptors",
|
|
860
1043
|
DEFAULT_DESCRIBE: "Test suite",
|
|
861
1044
|
NO_TESTS: "No saved tests",
|
|
862
|
-
DELETE_TITLE: "Delete"
|
|
1045
|
+
DELETE_TITLE: "Delete",
|
|
1046
|
+
GROUP_BY_TICKET: "\u{1F3AB} Group by ticket",
|
|
1047
|
+
NO_TICKET_GROUP: "No ticket"
|
|
863
1048
|
},
|
|
864
1049
|
CONFIG: {
|
|
865
1050
|
LANG_SECTION: "\u{1F310} Language",
|
|
@@ -867,6 +1052,9 @@ var I18N_EN = {
|
|
|
867
1052
|
HTTP_SECTION: "\u26A1 Advanced HTTP",
|
|
868
1053
|
HTTP_TITLE: "Body validations",
|
|
869
1054
|
HTTP_SUB: "GET \u2192 response \xB7 POST/PUT \u2192 request",
|
|
1055
|
+
FIXTURE_SECTION: "\u{1F9EA} HTTP Fixtures",
|
|
1056
|
+
FIXTURE_TITLE: "Record responses as fixtures",
|
|
1057
|
+
FIXTURE_SUB: "GET responses are saved to cypress/fixtures and stubbed via cy.intercept({ fixture }).",
|
|
870
1058
|
SELECTOR_SECTION: "\u{1F3AF} Selector strategy",
|
|
871
1059
|
SELECTOR_LABEL: "Preferred attribute for cy.get()",
|
|
872
1060
|
SELECTOR_OPT_DATACY: "data-cy (default)",
|
|
@@ -914,7 +1102,27 @@ var I18N_EN = {
|
|
|
914
1102
|
WIDGET_POSITION_SECTION: "\u{1F9F2} Widget position",
|
|
915
1103
|
WIDGET_POSITION_HINT: "Drag the record button to move it out of the way. Its position is remembered.",
|
|
916
1104
|
WIDGET_POSITION_RESET_BTN: "\u21BA Reset position",
|
|
917
|
-
WIDGET_POSITION_RESET_DONE: "\u2713 Position reset"
|
|
1105
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position reset",
|
|
1106
|
+
ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue tracker",
|
|
1107
|
+
ISSUE_TRACKER_LABEL: "Tracker (link only, no sync)",
|
|
1108
|
+
ISSUE_TRACKER_DISCLAIMER: "Builds links only \u2014 it never connects to or syncs with the platform.",
|
|
1109
|
+
ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
|
|
1110
|
+
ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
|
|
1111
|
+
ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
|
|
1112
|
+
ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
|
|
1113
|
+
ISSUE_TRACKER_PROVIDER_CUSTOM: "Custom",
|
|
1114
|
+
ISSUE_TRACKER_FIELD_BASEURL: "Base URL",
|
|
1115
|
+
ISSUE_TRACKER_FIELD_ORG: "Organization",
|
|
1116
|
+
ISSUE_TRACKER_FIELD_PROJECT: "Project",
|
|
1117
|
+
ISSUE_TRACKER_FIELD_OWNER: "Owner",
|
|
1118
|
+
ISSUE_TRACKER_FIELD_REPO: "Repository",
|
|
1119
|
+
ISSUE_TRACKER_FIELD_TEMPLATE: "URL template (must contain {id})",
|
|
1120
|
+
ISSUE_TRACKER_PH_JIRA_BASEURL: "https://your-org.atlassian.net",
|
|
1121
|
+
ISSUE_TRACKER_PH_AZURE_ORG: "organization",
|
|
1122
|
+
ISSUE_TRACKER_PH_AZURE_PROJECT: "project",
|
|
1123
|
+
ISSUE_TRACKER_PH_GITHUB_OWNER: "owner",
|
|
1124
|
+
ISSUE_TRACKER_PH_GITHUB_REPO: "repository",
|
|
1125
|
+
ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
|
|
918
1126
|
},
|
|
919
1127
|
SELECTOR_PICKER: {
|
|
920
1128
|
TITLE: "Select a DOM element",
|
|
@@ -1003,6 +1211,8 @@ var I18N_EN = {
|
|
|
1003
1211
|
FILE_EDITOR_TITLE: "\u270F\uFE0F File editor",
|
|
1004
1212
|
FILE_SAVED_TOAST: "\u2713 File saved",
|
|
1005
1213
|
FILE_SAVE_ERROR_TOAST: "Error saving the file",
|
|
1214
|
+
FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures written to cypress/fixtures",
|
|
1215
|
+
FIXTURES_NO_FOLDER_TOAST: "Could not write fixtures (configure the Cypress folder)",
|
|
1006
1216
|
BTN_CONFIG: "Config",
|
|
1007
1217
|
BTN_FILES: "Files",
|
|
1008
1218
|
BTN_COMMANDS: "Commands",
|
|
@@ -1017,7 +1227,76 @@ var I18N_EN = {
|
|
|
1017
1227
|
SESSION_RESUME_TEXT: "There is an unfinished active recording.",
|
|
1018
1228
|
SESSION_RESUME_COUNT: "commands captured",
|
|
1019
1229
|
SESSION_CONTINUE_BTN: "Keep recording",
|
|
1020
|
-
SESSION_DISCARD_BTN: "Discard"
|
|
1230
|
+
SESSION_DISCARD_BTN: "Discard",
|
|
1231
|
+
BTN_HELP: "Help"
|
|
1232
|
+
},
|
|
1233
|
+
HELP: {
|
|
1234
|
+
TITLE: "\u2753 Help \u2014 Quick guide",
|
|
1235
|
+
INTRO: "Everything the recorder can do.",
|
|
1236
|
+
SEC_RECORDING: "\u23FA Recording",
|
|
1237
|
+
RECORDING_1: "Press \u23FA (or Ctrl+R) to start; \u23F9 to stop and save.",
|
|
1238
|
+
RECORDING_2: "Pause / resume with \u23F8 (or Ctrl+P).",
|
|
1239
|
+
SEC_SHORTCUTS: "\u2328\uFE0F Keyboard shortcuts",
|
|
1240
|
+
SC_RECORD: "Ctrl+R \u2014 Record / stop",
|
|
1241
|
+
SC_PAUSE: "Ctrl+P \u2014 Pause / resume",
|
|
1242
|
+
SC_TESTS: "Ctrl+1 \u2014 Saved tests panel",
|
|
1243
|
+
SC_COMMANDS: "Ctrl+2 \u2014 Commands panel",
|
|
1244
|
+
SC_CONFIG: "Ctrl+3 \u2014 Settings",
|
|
1245
|
+
SC_VISIBILITY: "Ctrl+Shift+E \u2014 Show / hide the widget",
|
|
1246
|
+
SC_HELP: "Ctrl+Shift+H \u2014 This help",
|
|
1247
|
+
SEC_CAPTURED: "\u{1F3AF} What gets recorded",
|
|
1248
|
+
CAP_CLICK: "Click \u2192 .click()",
|
|
1249
|
+
CAP_DBLCLICK: "Double-click \u2192 .dblclick()",
|
|
1250
|
+
CAP_RIGHTCLICK: "Right-click \u2192 .rightclick()",
|
|
1251
|
+
CAP_TYPE: "Type into a field \u2192 .clear().type(...)",
|
|
1252
|
+
CAP_CHECK: "Checkbox / radio \u2192 .check() / .uncheck()",
|
|
1253
|
+
CAP_SELECT: "<select> \u2192 .select(...)",
|
|
1254
|
+
CAP_KEYS: 'Enter / Escape \u2192 .type("{enter}") / ("{esc}")',
|
|
1255
|
+
CAP_ROUTE: "Route change (SPA) \u2192 cy.url().should(...)",
|
|
1256
|
+
CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
|
|
1257
|
+
SEC_ASSERT: "\u2705 Assertions",
|
|
1258
|
+
ASSERT_1: "Alt+click an element to verify it without running its action (text, value, checked or visible).",
|
|
1259
|
+
ASSERT_2: "Or use the assertion builder in the Commands panel.",
|
|
1260
|
+
SEC_PANELS: "\u{1F5C2} Panels",
|
|
1261
|
+
PANEL_TESTS: "\u{1F4CB} Tests \u2014 saved tests: filter by tag, copy, combine, export.",
|
|
1262
|
+
PANEL_COMMANDS: "\u2328\uFE0F Commands \u2014 current recording: reorder, delete, add assertions.",
|
|
1263
|
+
PANEL_CONFIG: "\u2699\uFE0F Config \u2014 language, selector, HTTP, Cypress folder, data, visibility, position.",
|
|
1264
|
+
PANEL_FILES: "\u{1F4C1} Files \u2014 insert tests into your .cy.ts and launch the runner.",
|
|
1265
|
+
SEC_SELECTOR: "\u{1F50E} Selectors",
|
|
1266
|
+
SELECTOR_1: "Choose the preferred attribute (data-cy, data-testid, aria-label, id) in Config.",
|
|
1267
|
+
SELECTOR_2: "If a click has no reliable selector, a picker appears to choose the best ancestor.",
|
|
1268
|
+
SEC_DATA: "\u{1F4BE} Data",
|
|
1269
|
+
DATA_1: "Export tests to JSON (all / selection / by tags).",
|
|
1270
|
+
DATA_2: "Import a JSON: it merges into your data (nothing is wiped).",
|
|
1271
|
+
SEC_ADVANCED: "\u{1F680} Advanced",
|
|
1272
|
+
ADV_INVISIBLE: "Invisible mode: start-hidden + Ctrl+Shift+E to record in production unobtrusively.",
|
|
1273
|
+
ADV_RUNNER: "Local runner: npx lib-e2e-cypress-runner to run a spec headless from the editor.",
|
|
1274
|
+
ADV_CROSSAPP: "Cross-app: the recording survives switching micro-frontend (same origin).",
|
|
1275
|
+
ADV_DRAG: "Drag the \u23FA button to move the widget; its position is remembered.",
|
|
1276
|
+
CLOSE: "Close",
|
|
1277
|
+
TAB_REFERENCE: "Quick reference",
|
|
1278
|
+
TAB_GUIDE: "Usage guide",
|
|
1279
|
+
GUIDE_INTRO: "How to get the most out of the recorder, and what it does / doesn't cover.",
|
|
1280
|
+
G_SEC_WORKFLOW: "\u{1F9ED} Workflow",
|
|
1281
|
+
G_WF_1: "1. Press \u23FA (or Ctrl+R) to start recording.",
|
|
1282
|
+
G_WF_2: "2. Use your app like a user: clicks, forms, navigation. Each action becomes a Cypress command.",
|
|
1283
|
+
G_WF_3: "3. Add checks with Alt+click on whatever you want to verify.",
|
|
1284
|
+
G_WF_4: "4. Press \u23F9 to stop; give it a name and tags, and save it.",
|
|
1285
|
+
G_WF_5: "5. In \u{1F4C1} Files, insert the test into a .cy.ts or run it with the local runner.",
|
|
1286
|
+
G_SEC_COVERED: "\u2705 What it covers",
|
|
1287
|
+
G_COV_1: "Clicks, double-click, right-click, text inputs, checkbox/radio, select and Enter/Escape keys.",
|
|
1288
|
+
G_COV_2: "SPA navigation (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
|
|
1289
|
+
G_COV_3: "fetch/XHR calls (GET/POST/PUT) \u2192 cy.intercept + cy.wait, with optional body validation.",
|
|
1290
|
+
G_COV_4: "Reliable selector (data-cy/testid/aria/id) and a picker when none is obvious.",
|
|
1291
|
+
G_COV_5: "Automatic assertions (Alt+click) and the manual builder.",
|
|
1292
|
+
G_COV_6: "Continuity when switching micro-frontend (same origin), invisible mode and i18n.",
|
|
1293
|
+
G_SEC_NOTCOVERED: "\u{1F6AB} What it does NOT cover (yet)",
|
|
1294
|
+
G_NC_1: "Different origins / subdomains (same origin only).",
|
|
1295
|
+
G_NC_2: "File upload (selectFile), drag & drop, and hover.",
|
|
1296
|
+
G_NC_3: "The Tab key (Cypress can't .type() it).",
|
|
1297
|
+
G_NC_4: "DELETE requests (intentionally ignored).",
|
|
1298
|
+
G_NC_5: "Third-party iframes and Shadow DOM may not be captured.",
|
|
1299
|
+
G_NC_6: "Programmatic login generation (on the roadmap)."
|
|
1021
1300
|
}
|
|
1022
1301
|
};
|
|
1023
1302
|
|
|
@@ -1045,7 +1324,14 @@ var I18N_FR = {
|
|
|
1045
1324
|
SAVE_AND_EDIT: "\u{1F4DD} Enregistrer et \xE9diter",
|
|
1046
1325
|
REMOVE_TAG_TITLE: "Retirer",
|
|
1047
1326
|
NOTES_LABEL: "Description / Notes (optionnel) :",
|
|
1048
|
-
NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc."
|
|
1327
|
+
NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc.",
|
|
1328
|
+
TICKET_LABEL: "Ticket (optionnel) :",
|
|
1329
|
+
TICKET_PH_JIRA: "p. ex. PROJ-123",
|
|
1330
|
+
TICKET_PH_AZURE: "p. ex. 1234",
|
|
1331
|
+
TICKET_PH_GITHUB: "p. ex. 42",
|
|
1332
|
+
TICKET_PH_TRELLO: "p. ex. aBc123",
|
|
1333
|
+
TICKET_PH_CUSTOM: "id du ticket",
|
|
1334
|
+
TICKET_INVALID: "L'id du ticket ne correspond pas au format attendu par le gestionnaire s\xE9lectionn\xE9."
|
|
1049
1335
|
},
|
|
1050
1336
|
TEST_EDITOR: {
|
|
1051
1337
|
NO_TAGS: "Sans \xE9tiquettes",
|
|
@@ -1059,7 +1345,9 @@ var I18N_FR = {
|
|
|
1059
1345
|
COPY_ICPS_BTN: "\u{1F4CB} Copier les intercepteurs",
|
|
1060
1346
|
DEFAULT_DESCRIBE: "Suite de tests",
|
|
1061
1347
|
NO_TESTS: "Aucun test enregistr\xE9",
|
|
1062
|
-
DELETE_TITLE: "Supprimer"
|
|
1348
|
+
DELETE_TITLE: "Supprimer",
|
|
1349
|
+
GROUP_BY_TICKET: "\u{1F3AB} Grouper par ticket",
|
|
1350
|
+
NO_TICKET_GROUP: "Sans ticket"
|
|
1063
1351
|
},
|
|
1064
1352
|
CONFIG: {
|
|
1065
1353
|
LANG_SECTION: "\u{1F310} Langue",
|
|
@@ -1067,6 +1355,9 @@ var I18N_FR = {
|
|
|
1067
1355
|
HTTP_SECTION: "\u26A1 HTTP Avanc\xE9",
|
|
1068
1356
|
HTTP_TITLE: "Validations du corps",
|
|
1069
1357
|
HTTP_SUB: "GET \u2192 r\xE9ponse \xB7 POST/PUT \u2192 requ\xEAte",
|
|
1358
|
+
FIXTURE_SECTION: "\u{1F9EA} Fixtures HTTP",
|
|
1359
|
+
FIXTURE_TITLE: "Enregistrer les r\xE9ponses comme fixtures",
|
|
1360
|
+
FIXTURE_SUB: "Les GET sont enregistr\xE9es dans cypress/fixtures et stub\xE9es via cy.intercept({ fixture }).",
|
|
1070
1361
|
SELECTOR_SECTION: "\u{1F3AF} Strat\xE9gie de s\xE9lecteur",
|
|
1071
1362
|
SELECTOR_LABEL: "Attribut pr\xE9f\xE9r\xE9 pour cy.get()",
|
|
1072
1363
|
SELECTOR_OPT_DATACY: "data-cy (par d\xE9faut)",
|
|
@@ -1114,7 +1405,27 @@ var I18N_FR = {
|
|
|
1114
1405
|
WIDGET_POSITION_SECTION: "\u{1F9F2} Position du widget",
|
|
1115
1406
|
WIDGET_POSITION_HINT: "Fais glisser le bouton d'enregistrement pour le d\xE9placer. Sa position est m\xE9moris\xE9e.",
|
|
1116
1407
|
WIDGET_POSITION_RESET_BTN: "\u21BA R\xE9initialiser la position",
|
|
1117
|
-
WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e"
|
|
1408
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e",
|
|
1409
|
+
ISSUE_TRACKER_SECTION: "\u{1F3AB} Gestionnaire de tickets",
|
|
1410
|
+
ISSUE_TRACKER_LABEL: "Gestionnaire (lien uniquement, sans synchronisation)",
|
|
1411
|
+
ISSUE_TRACKER_DISCLAIMER: "G\xE9n\xE8re uniquement des liens \u2014 ne se connecte ni ne se synchronise jamais avec la plateforme.",
|
|
1412
|
+
ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
|
|
1413
|
+
ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
|
|
1414
|
+
ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
|
|
1415
|
+
ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
|
|
1416
|
+
ISSUE_TRACKER_PROVIDER_CUSTOM: "Personnalis\xE9",
|
|
1417
|
+
ISSUE_TRACKER_FIELD_BASEURL: "URL de base",
|
|
1418
|
+
ISSUE_TRACKER_FIELD_ORG: "Organisation",
|
|
1419
|
+
ISSUE_TRACKER_FIELD_PROJECT: "Projet",
|
|
1420
|
+
ISSUE_TRACKER_FIELD_OWNER: "Propri\xE9taire",
|
|
1421
|
+
ISSUE_TRACKER_FIELD_REPO: "D\xE9p\xF4t",
|
|
1422
|
+
ISSUE_TRACKER_FIELD_TEMPLATE: "Mod\xE8le d'URL (doit contenir {id})",
|
|
1423
|
+
ISSUE_TRACKER_PH_JIRA_BASEURL: "https://votre-org.atlassian.net",
|
|
1424
|
+
ISSUE_TRACKER_PH_AZURE_ORG: "organisation",
|
|
1425
|
+
ISSUE_TRACKER_PH_AZURE_PROJECT: "projet",
|
|
1426
|
+
ISSUE_TRACKER_PH_GITHUB_OWNER: "propri\xE9taire",
|
|
1427
|
+
ISSUE_TRACKER_PH_GITHUB_REPO: "d\xE9p\xF4t",
|
|
1428
|
+
ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
|
|
1118
1429
|
},
|
|
1119
1430
|
SELECTOR_PICKER: {
|
|
1120
1431
|
TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
|
|
@@ -1203,6 +1514,8 @@ var I18N_FR = {
|
|
|
1203
1514
|
FILE_EDITOR_TITLE: "\u270F\uFE0F \xC9diteur de fichier",
|
|
1204
1515
|
FILE_SAVED_TOAST: "\u2713 Fichier enregistr\xE9",
|
|
1205
1516
|
FILE_SAVE_ERROR_TOAST: "Erreur lors de l'enregistrement",
|
|
1517
|
+
FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures \xE9crites dans cypress/fixtures",
|
|
1518
|
+
FIXTURES_NO_FOLDER_TOAST: "Impossible d'\xE9crire les fixtures (configure le dossier Cypress)",
|
|
1206
1519
|
BTN_CONFIG: "Config",
|
|
1207
1520
|
BTN_FILES: "Fichiers",
|
|
1208
1521
|
BTN_COMMANDS: "Commandes",
|
|
@@ -1217,7 +1530,77 @@ var I18N_FR = {
|
|
|
1217
1530
|
SESSION_RESUME_TEXT: "Un enregistrement actif est inachev\xE9.",
|
|
1218
1531
|
SESSION_RESUME_COUNT: "commandes captur\xE9es",
|
|
1219
1532
|
SESSION_CONTINUE_BTN: "Continuer l'enregistrement",
|
|
1220
|
-
SESSION_DISCARD_BTN: "Ignorer"
|
|
1533
|
+
SESSION_DISCARD_BTN: "Ignorer",
|
|
1534
|
+
BTN_HELP: "Aide"
|
|
1535
|
+
},
|
|
1536
|
+
HELP: {
|
|
1537
|
+
TITLE: "\u2753 Aide \u2014 Guide rapide",
|
|
1538
|
+
INTRO: "Tout ce que l'enregistreur sait faire.",
|
|
1539
|
+
SEC_RECORDING: "\u23FA Enregistrer",
|
|
1540
|
+
RECORDING_1: "Appuie sur \u23FA (ou Ctrl+R) pour commencer ; \u23F9 pour arr\xEAter et enregistrer.",
|
|
1541
|
+
RECORDING_2: "Pause / reprise avec \u23F8 (ou Ctrl+P).",
|
|
1542
|
+
SEC_SHORTCUTS: "\u2328\uFE0F Raccourcis clavier",
|
|
1543
|
+
SC_RECORD: "Ctrl+R \u2014 Enregistrer / arr\xEAter",
|
|
1544
|
+
SC_PAUSE: "Ctrl+P \u2014 Pause / reprise",
|
|
1545
|
+
SC_TESTS: "Ctrl+1 \u2014 Panneau des tests enregistr\xE9s",
|
|
1546
|
+
SC_COMMANDS: "Ctrl+2 \u2014 Panneau des commandes",
|
|
1547
|
+
SC_CONFIG: "Ctrl+3 \u2014 Param\xE8tres",
|
|
1548
|
+
SC_VISIBILITY: "Ctrl+Shift+E \u2014 Afficher / masquer le widget",
|
|
1549
|
+
SC_HELP: "Ctrl+Shift+H \u2014 Cette aide",
|
|
1550
|
+
SEC_CAPTURED: "\u{1F3AF} Ce qui est enregistr\xE9",
|
|
1551
|
+
CAP_CLICK: "Clic \u2192 .click()",
|
|
1552
|
+
CAP_DBLCLICK: "Double-clic \u2192 .dblclick()",
|
|
1553
|
+
CAP_RIGHTCLICK: "Clic droit \u2192 .rightclick()",
|
|
1554
|
+
CAP_TYPE: "Saisie dans un champ \u2192 .clear().type(...)",
|
|
1555
|
+
CAP_CHECK: "Case \xE0 cocher / radio \u2192 .check() / .uncheck()",
|
|
1556
|
+
CAP_SELECT: "<select> \u2192 .select(...)",
|
|
1557
|
+
CAP_KEYS: 'Entr\xE9e / \xC9chap \u2192 .type("{enter}") / ("{esc}")',
|
|
1558
|
+
CAP_ROUTE: "Changement de route (SPA) \u2192 cy.url().should(...)",
|
|
1559
|
+
CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
|
|
1560
|
+
SEC_ASSERT: "\u2705 Assertions",
|
|
1561
|
+
ASSERT_1: "Alt+clic sur un \xE9l\xE9ment pour le v\xE9rifier sans ex\xE9cuter son action (texte, valeur, coch\xE9 ou visible).",
|
|
1562
|
+
ASSERT_2: "Ou utilise le constructeur d'assertions dans le panneau Commandes.",
|
|
1563
|
+
SEC_PANELS: "\u{1F5C2} Panneaux",
|
|
1564
|
+
PANEL_TESTS: "\u{1F4CB} Tests \u2014 enregistr\xE9s : filtrer par tag, copier, combiner, exporter.",
|
|
1565
|
+
PANEL_COMMANDS: "\u2328\uFE0F Commandes \u2014 enregistrement en cours : r\xE9ordonner, supprimer, ajouter des assertions.",
|
|
1566
|
+
PANEL_CONFIG: "\u2699\uFE0F Config \u2014 langue, s\xE9lecteur, HTTP, dossier Cypress, donn\xE9es, visibilit\xE9, position.",
|
|
1567
|
+
PANEL_FILES: "\u{1F4C1} Fichiers \u2014 ins\xE8re des tests dans tes .cy.ts et lance le runner.",
|
|
1568
|
+
SEC_SELECTOR: "\u{1F50E} S\xE9lecteurs",
|
|
1569
|
+
SELECTOR_1: "Choisis l'attribut pr\xE9f\xE9r\xE9 (data-cy, data-testid, aria-label, id) dans Config.",
|
|
1570
|
+
SELECTOR_2: "Si un clic n'a pas de s\xE9lecteur fiable, un s\xE9lecteur appara\xEEt pour choisir le meilleur anc\xEAtre.",
|
|
1571
|
+
SEC_DATA: "\u{1F4BE} Donn\xE9es",
|
|
1572
|
+
DATA_1: "Exporte les tests en JSON (tout / s\xE9lection / par tags).",
|
|
1573
|
+
DATA_2: "Importe un JSON : il fusionne avec tes donn\xE9es (rien n'est effac\xE9).",
|
|
1574
|
+
SEC_ADVANCED: "\u{1F680} Avanc\xE9",
|
|
1575
|
+
ADV_INVISIBLE: "Mode invisible : start-hidden + Ctrl+Shift+E pour enregistrer en production discr\xE8tement.",
|
|
1576
|
+
ADV_RUNNER: "Runner local : npx lib-e2e-cypress-runner pour lancer un spec headless depuis l'\xE9diteur.",
|
|
1577
|
+
ADV_CROSSAPP: "Cross-app : l'enregistrement survit au changement de micro-frontend (m\xEAme origine).",
|
|
1578
|
+
ADV_DRAG: "Fais glisser le bouton \u23FA pour d\xE9placer le widget ; sa position est m\xE9moris\xE9e.",
|
|
1579
|
+
CLOSE: "Fermer",
|
|
1580
|
+
TAB_REFERENCE: "R\xE9f\xE9rence rapide",
|
|
1581
|
+
TAB_GUIDE: "Guide d'utilisation",
|
|
1582
|
+
// TODO: translate (fr) — usage guide body kept in Spanish for now.
|
|
1583
|
+
GUIDE_INTRO: "C\xF3mo sacarle partido al grabador, qu\xE9 cubre y qu\xE9 no.",
|
|
1584
|
+
G_SEC_WORKFLOW: "\u{1F9ED} Flujo de trabajo",
|
|
1585
|
+
G_WF_1: "1. Pulsa \u23FA (o Ctrl+R) para empezar a grabar.",
|
|
1586
|
+
G_WF_2: "2. Usa tu app como un usuario: clicks, formularios, navegaci\xF3n. Cada acci\xF3n se convierte en un comando Cypress.",
|
|
1587
|
+
G_WF_3: "3. A\xF1ade verificaciones con Alt+click sobre lo que quieras comprobar.",
|
|
1588
|
+
G_WF_4: "4. Pulsa \u23F9 para parar; ponle nombre y tags, y gu\xE1rdalo.",
|
|
1589
|
+
G_WF_5: "5. En \u{1F4C1} Ficheros, inserta el test en un .cy.ts o l\xE1nzalo con el runner local.",
|
|
1590
|
+
G_SEC_COVERED: "\u2705 Qu\xE9 cubre",
|
|
1591
|
+
G_COV_1: "Clicks, doble-click, click derecho, inputs de texto, checkbox/radio, select y teclas Enter/Escape.",
|
|
1592
|
+
G_COV_2: "Navegaci\xF3n SPA (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
|
|
1593
|
+
G_COV_3: "Llamadas fetch/XHR (GET/POST/PUT) \u2192 cy.intercept + cy.wait, con validaci\xF3n de body opcional.",
|
|
1594
|
+
G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
|
|
1595
|
+
G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
|
|
1596
|
+
G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
|
|
1597
|
+
G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
|
|
1598
|
+
G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
|
|
1599
|
+
G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
|
|
1600
|
+
G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
|
|
1601
|
+
G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
|
|
1602
|
+
G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
|
|
1603
|
+
G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
|
|
1221
1604
|
}
|
|
1222
1605
|
};
|
|
1223
1606
|
|
|
@@ -1245,7 +1628,14 @@ var I18N_IT = {
|
|
|
1245
1628
|
SAVE_AND_EDIT: "\u{1F4DD} Salva e modifica",
|
|
1246
1629
|
REMOVE_TAG_TITLE: "Rimuovi",
|
|
1247
1630
|
NOTES_LABEL: "Descrizione / Note (opzionale):",
|
|
1248
|
-
NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc."
|
|
1631
|
+
NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc.",
|
|
1632
|
+
TICKET_LABEL: "Ticket (opzionale):",
|
|
1633
|
+
TICKET_PH_JIRA: "es. PROJ-123",
|
|
1634
|
+
TICKET_PH_AZURE: "es. 1234",
|
|
1635
|
+
TICKET_PH_GITHUB: "es. 42",
|
|
1636
|
+
TICKET_PH_TRELLO: "es. aBc123",
|
|
1637
|
+
TICKET_PH_CUSTOM: "id del ticket",
|
|
1638
|
+
TICKET_INVALID: "L'id del ticket non corrisponde al formato previsto dal gestore selezionato."
|
|
1249
1639
|
},
|
|
1250
1640
|
TEST_EDITOR: {
|
|
1251
1641
|
NO_TAGS: "Nessuna etichetta",
|
|
@@ -1259,7 +1649,9 @@ var I18N_IT = {
|
|
|
1259
1649
|
COPY_ICPS_BTN: "\u{1F4CB} Copia interceptor",
|
|
1260
1650
|
DEFAULT_DESCRIBE: "Suite di test",
|
|
1261
1651
|
NO_TESTS: "Nessun test salvato",
|
|
1262
|
-
DELETE_TITLE: "Elimina"
|
|
1652
|
+
DELETE_TITLE: "Elimina",
|
|
1653
|
+
GROUP_BY_TICKET: "\u{1F3AB} Raggruppa per ticket",
|
|
1654
|
+
NO_TICKET_GROUP: "Senza ticket"
|
|
1263
1655
|
},
|
|
1264
1656
|
CONFIG: {
|
|
1265
1657
|
LANG_SECTION: "\u{1F310} Lingua",
|
|
@@ -1267,6 +1659,9 @@ var I18N_IT = {
|
|
|
1267
1659
|
HTTP_SECTION: "\u26A1 HTTP Avanzato",
|
|
1268
1660
|
HTTP_TITLE: "Validazioni del body",
|
|
1269
1661
|
HTTP_SUB: "GET \u2192 risposta \xB7 POST/PUT \u2192 richiesta",
|
|
1662
|
+
FIXTURE_SECTION: "\u{1F9EA} Fixture HTTP",
|
|
1663
|
+
FIXTURE_TITLE: "Registra le risposte come fixture",
|
|
1664
|
+
FIXTURE_SUB: "Le GET vengono salvate in cypress/fixtures e stubbate con cy.intercept({ fixture }).",
|
|
1270
1665
|
SELECTOR_SECTION: "\u{1F3AF} Strategia selettore",
|
|
1271
1666
|
SELECTOR_LABEL: "Attributo preferito per cy.get()",
|
|
1272
1667
|
SELECTOR_OPT_DATACY: "data-cy (predefinito)",
|
|
@@ -1314,7 +1709,27 @@ var I18N_IT = {
|
|
|
1314
1709
|
WIDGET_POSITION_SECTION: "\u{1F9F2} Posizione del widget",
|
|
1315
1710
|
WIDGET_POSITION_HINT: "Trascina il pulsante di registrazione per spostarlo. La posizione viene ricordata.",
|
|
1316
1711
|
WIDGET_POSITION_RESET_BTN: "\u21BA Reimposta posizione",
|
|
1317
|
-
WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata"
|
|
1712
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata",
|
|
1713
|
+
ISSUE_TRACKER_SECTION: "\u{1F3AB} Gestore ticket",
|
|
1714
|
+
ISSUE_TRACKER_LABEL: "Gestore (solo link, senza sincronizzazione)",
|
|
1715
|
+
ISSUE_TRACKER_DISCLAIMER: "Genera solo link \u2014 non si connette n\xE9 si sincronizza mai con la piattaforma.",
|
|
1716
|
+
ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
|
|
1717
|
+
ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
|
|
1718
|
+
ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
|
|
1719
|
+
ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
|
|
1720
|
+
ISSUE_TRACKER_PROVIDER_CUSTOM: "Personalizzato",
|
|
1721
|
+
ISSUE_TRACKER_FIELD_BASEURL: "URL di base",
|
|
1722
|
+
ISSUE_TRACKER_FIELD_ORG: "Organizzazione",
|
|
1723
|
+
ISSUE_TRACKER_FIELD_PROJECT: "Progetto",
|
|
1724
|
+
ISSUE_TRACKER_FIELD_OWNER: "Proprietario",
|
|
1725
|
+
ISSUE_TRACKER_FIELD_REPO: "Repository",
|
|
1726
|
+
ISSUE_TRACKER_FIELD_TEMPLATE: "Modello di URL (deve contenere {id})",
|
|
1727
|
+
ISSUE_TRACKER_PH_JIRA_BASEURL: "https://tua-org.atlassian.net",
|
|
1728
|
+
ISSUE_TRACKER_PH_AZURE_ORG: "organizzazione",
|
|
1729
|
+
ISSUE_TRACKER_PH_AZURE_PROJECT: "progetto",
|
|
1730
|
+
ISSUE_TRACKER_PH_GITHUB_OWNER: "proprietario",
|
|
1731
|
+
ISSUE_TRACKER_PH_GITHUB_REPO: "repository",
|
|
1732
|
+
ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
|
|
1318
1733
|
},
|
|
1319
1734
|
SELECTOR_PICKER: {
|
|
1320
1735
|
TITLE: "Seleziona un elemento DOM",
|
|
@@ -1403,6 +1818,8 @@ var I18N_IT = {
|
|
|
1403
1818
|
FILE_EDITOR_TITLE: "\u270F\uFE0F Editor di file",
|
|
1404
1819
|
FILE_SAVED_TOAST: "\u2713 File salvato",
|
|
1405
1820
|
FILE_SAVE_ERROR_TOAST: "Errore durante il salvataggio",
|
|
1821
|
+
FIXTURES_WRITTEN_TOAST: "\u2713 Fixture scritte in cypress/fixtures",
|
|
1822
|
+
FIXTURES_NO_FOLDER_TOAST: "Impossibile scrivere le fixture (configura la cartella Cypress)",
|
|
1406
1823
|
BTN_CONFIG: "Config",
|
|
1407
1824
|
BTN_FILES: "File",
|
|
1408
1825
|
BTN_COMMANDS: "Comandi",
|
|
@@ -1417,7 +1834,77 @@ var I18N_IT = {
|
|
|
1417
1834
|
SESSION_RESUME_TEXT: "C'\xE8 una registrazione attiva non terminata.",
|
|
1418
1835
|
SESSION_RESUME_COUNT: "comandi catturati",
|
|
1419
1836
|
SESSION_CONTINUE_BTN: "Continua a registrare",
|
|
1420
|
-
SESSION_DISCARD_BTN: "Annulla"
|
|
1837
|
+
SESSION_DISCARD_BTN: "Annulla",
|
|
1838
|
+
BTN_HELP: "Aiuto"
|
|
1839
|
+
},
|
|
1840
|
+
HELP: {
|
|
1841
|
+
TITLE: "\u2753 Aiuto \u2014 Guida rapida",
|
|
1842
|
+
INTRO: "Tutto ci\xF2 che il registratore sa fare.",
|
|
1843
|
+
SEC_RECORDING: "\u23FA Registrare",
|
|
1844
|
+
RECORDING_1: "Premi \u23FA (o Ctrl+R) per iniziare; \u23F9 per fermare e salvare.",
|
|
1845
|
+
RECORDING_2: "Pausa / ripresa con \u23F8 (o Ctrl+P).",
|
|
1846
|
+
SEC_SHORTCUTS: "\u2328\uFE0F Scorciatoie da tastiera",
|
|
1847
|
+
SC_RECORD: "Ctrl+R \u2014 Registra / ferma",
|
|
1848
|
+
SC_PAUSE: "Ctrl+P \u2014 Pausa / ripresa",
|
|
1849
|
+
SC_TESTS: "Ctrl+1 \u2014 Pannello test salvati",
|
|
1850
|
+
SC_COMMANDS: "Ctrl+2 \u2014 Pannello comandi",
|
|
1851
|
+
SC_CONFIG: "Ctrl+3 \u2014 Impostazioni",
|
|
1852
|
+
SC_VISIBILITY: "Ctrl+Shift+E \u2014 Mostra / nascondi il widget",
|
|
1853
|
+
SC_HELP: "Ctrl+Shift+H \u2014 Questo aiuto",
|
|
1854
|
+
SEC_CAPTURED: "\u{1F3AF} Cosa viene registrato",
|
|
1855
|
+
CAP_CLICK: "Click \u2192 .click()",
|
|
1856
|
+
CAP_DBLCLICK: "Doppio click \u2192 .dblclick()",
|
|
1857
|
+
CAP_RIGHTCLICK: "Click destro \u2192 .rightclick()",
|
|
1858
|
+
CAP_TYPE: "Digitare in un campo \u2192 .clear().type(...)",
|
|
1859
|
+
CAP_CHECK: "Checkbox / radio \u2192 .check() / .uncheck()",
|
|
1860
|
+
CAP_SELECT: "<select> \u2192 .select(...)",
|
|
1861
|
+
CAP_KEYS: 'Invio / Esc \u2192 .type("{enter}") / ("{esc}")',
|
|
1862
|
+
CAP_ROUTE: "Cambio di rotta (SPA) \u2192 cy.url().should(...)",
|
|
1863
|
+
CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
|
|
1864
|
+
SEC_ASSERT: "\u2705 Asserzioni",
|
|
1865
|
+
ASSERT_1: "Alt+click su un elemento per verificarlo senza eseguire la sua azione (testo, valore, checked o visibile).",
|
|
1866
|
+
ASSERT_2: "Oppure usa il costruttore di asserzioni nel pannello Comandi.",
|
|
1867
|
+
SEC_PANELS: "\u{1F5C2} Pannelli",
|
|
1868
|
+
PANEL_TESTS: "\u{1F4CB} Test \u2014 salvati: filtra per tag, copia, combina, esporta.",
|
|
1869
|
+
PANEL_COMMANDS: "\u2328\uFE0F Comandi \u2014 registrazione corrente: riordina, elimina, aggiungi asserzioni.",
|
|
1870
|
+
PANEL_CONFIG: "\u2699\uFE0F Config \u2014 lingua, selettore, HTTP, cartella Cypress, dati, visibilit\xE0, posizione.",
|
|
1871
|
+
PANEL_FILES: "\u{1F4C1} File \u2014 inserisci test nei tuoi .cy.ts e avvia il runner.",
|
|
1872
|
+
SEC_SELECTOR: "\u{1F50E} Selettori",
|
|
1873
|
+
SELECTOR_1: "Scegli l'attributo preferito (data-cy, data-testid, aria-label, id) in Config.",
|
|
1874
|
+
SELECTOR_2: "Se un click non ha un selettore affidabile, appare un picker per scegliere l'antenato migliore.",
|
|
1875
|
+
SEC_DATA: "\u{1F4BE} Dati",
|
|
1876
|
+
DATA_1: "Esporta i test in JSON (tutto / selezione / per tag).",
|
|
1877
|
+
DATA_2: "Importa un JSON: si fonde con i tuoi dati (non si cancella nulla).",
|
|
1878
|
+
SEC_ADVANCED: "\u{1F680} Avanzato",
|
|
1879
|
+
ADV_INVISIBLE: "Modalit\xE0 invisibile: start-hidden + Ctrl+Shift+E per registrare in produzione senza disturbare.",
|
|
1880
|
+
ADV_RUNNER: "Runner locale: npx lib-e2e-cypress-runner per eseguire uno spec headless dall'editor.",
|
|
1881
|
+
ADV_CROSSAPP: "Cross-app: la registrazione sopravvive al cambio di micro-frontend (stessa origine).",
|
|
1882
|
+
ADV_DRAG: "Trascina il pulsante \u23FA per spostare il widget; la posizione viene ricordata.",
|
|
1883
|
+
CLOSE: "Chiudi",
|
|
1884
|
+
TAB_REFERENCE: "Riferimento rapido",
|
|
1885
|
+
TAB_GUIDE: "Guida all'uso",
|
|
1886
|
+
// TODO: translate (it) — usage guide body kept in Spanish for now.
|
|
1887
|
+
GUIDE_INTRO: "C\xF3mo sacarle partido al grabador, qu\xE9 cubre y qu\xE9 no.",
|
|
1888
|
+
G_SEC_WORKFLOW: "\u{1F9ED} Flujo de trabajo",
|
|
1889
|
+
G_WF_1: "1. Pulsa \u23FA (o Ctrl+R) para empezar a grabar.",
|
|
1890
|
+
G_WF_2: "2. Usa tu app como un usuario: clicks, formularios, navegaci\xF3n. Cada acci\xF3n se convierte en un comando Cypress.",
|
|
1891
|
+
G_WF_3: "3. A\xF1ade verificaciones con Alt+click sobre lo que quieras comprobar.",
|
|
1892
|
+
G_WF_4: "4. Pulsa \u23F9 para parar; ponle nombre y tags, y gu\xE1rdalo.",
|
|
1893
|
+
G_WF_5: "5. En \u{1F4C1} Ficheros, inserta el test en un .cy.ts o l\xE1nzalo con el runner local.",
|
|
1894
|
+
G_SEC_COVERED: "\u2705 Qu\xE9 cubre",
|
|
1895
|
+
G_COV_1: "Clicks, doble-click, click derecho, inputs de texto, checkbox/radio, select y teclas Enter/Escape.",
|
|
1896
|
+
G_COV_2: "Navegaci\xF3n SPA (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
|
|
1897
|
+
G_COV_3: "Llamadas fetch/XHR (GET/POST/PUT) \u2192 cy.intercept + cy.wait, con validaci\xF3n de body opcional.",
|
|
1898
|
+
G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
|
|
1899
|
+
G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
|
|
1900
|
+
G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
|
|
1901
|
+
G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
|
|
1902
|
+
G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
|
|
1903
|
+
G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
|
|
1904
|
+
G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
|
|
1905
|
+
G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
|
|
1906
|
+
G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
|
|
1907
|
+
G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
|
|
1421
1908
|
}
|
|
1422
1909
|
};
|
|
1423
1910
|
|
|
@@ -1445,7 +1932,14 @@ var I18N_DE = {
|
|
|
1445
1932
|
SAVE_AND_EDIT: "\u{1F4DD} Speichern und bearbeiten",
|
|
1446
1933
|
REMOVE_TAG_TITLE: "Entfernen",
|
|
1447
1934
|
NOTES_LABEL: "Beschreibung / Notizen (optional):",
|
|
1448
|
-
NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw."
|
|
1935
|
+
NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw.",
|
|
1936
|
+
TICKET_LABEL: "Ticket (optional):",
|
|
1937
|
+
TICKET_PH_JIRA: "z. B. PROJ-123",
|
|
1938
|
+
TICKET_PH_AZURE: "z. B. 1234",
|
|
1939
|
+
TICKET_PH_GITHUB: "z. B. 42",
|
|
1940
|
+
TICKET_PH_TRELLO: "z. B. aBc123",
|
|
1941
|
+
TICKET_PH_CUSTOM: "Ticket-ID",
|
|
1942
|
+
TICKET_INVALID: "Die Ticket-ID entspricht nicht dem erwarteten Format des ausgew\xE4hlten Trackers."
|
|
1449
1943
|
},
|
|
1450
1944
|
TEST_EDITOR: {
|
|
1451
1945
|
NO_TAGS: "Keine Tags",
|
|
@@ -1459,7 +1953,9 @@ var I18N_DE = {
|
|
|
1459
1953
|
COPY_ICPS_BTN: "\u{1F4CB} Interceptors kopieren",
|
|
1460
1954
|
DEFAULT_DESCRIBE: "Test-Suite",
|
|
1461
1955
|
NO_TESTS: "Keine gespeicherten Tests",
|
|
1462
|
-
DELETE_TITLE: "L\xF6schen"
|
|
1956
|
+
DELETE_TITLE: "L\xF6schen",
|
|
1957
|
+
GROUP_BY_TICKET: "\u{1F3AB} Nach Ticket gruppieren",
|
|
1958
|
+
NO_TICKET_GROUP: "Kein Ticket"
|
|
1463
1959
|
},
|
|
1464
1960
|
CONFIG: {
|
|
1465
1961
|
LANG_SECTION: "\u{1F310} Sprache",
|
|
@@ -1467,6 +1963,9 @@ var I18N_DE = {
|
|
|
1467
1963
|
HTTP_SECTION: "\u26A1 Erweitertes HTTP",
|
|
1468
1964
|
HTTP_TITLE: "Body-Validierungen",
|
|
1469
1965
|
HTTP_SUB: "GET \u2192 Antwort \xB7 POST/PUT \u2192 Anfrage",
|
|
1966
|
+
FIXTURE_SECTION: "\u{1F9EA} HTTP-Fixtures",
|
|
1967
|
+
FIXTURE_TITLE: "Antworten als Fixtures aufzeichnen",
|
|
1968
|
+
FIXTURE_SUB: "GET-Antworten werden in cypress/fixtures gespeichert und mit cy.intercept({ fixture }) gestubbt.",
|
|
1470
1969
|
SELECTOR_SECTION: "\u{1F3AF} Selector-Strategie",
|
|
1471
1970
|
SELECTOR_LABEL: "Bevorzugtes Attribut f\xFCr cy.get()",
|
|
1472
1971
|
SELECTOR_OPT_DATACY: "data-cy (Standard)",
|
|
@@ -1514,7 +2013,27 @@ var I18N_DE = {
|
|
|
1514
2013
|
WIDGET_POSITION_SECTION: "\u{1F9F2} Widget-Position",
|
|
1515
2014
|
WIDGET_POSITION_HINT: "Ziehe den Aufnahme-Button, um ihn aus dem Weg zu schieben. Die Position wird gespeichert.",
|
|
1516
2015
|
WIDGET_POSITION_RESET_BTN: "\u21BA Position zur\xFCcksetzen",
|
|
1517
|
-
WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt"
|
|
2016
|
+
WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt",
|
|
2017
|
+
ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue-Tracker",
|
|
2018
|
+
ISSUE_TRACKER_LABEL: "Tracker (nur Link, keine Synchronisierung)",
|
|
2019
|
+
ISSUE_TRACKER_DISCLAIMER: "Erstellt nur Links \u2014 verbindet oder synchronisiert sich nie mit der Plattform.",
|
|
2020
|
+
ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
|
|
2021
|
+
ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
|
|
2022
|
+
ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
|
|
2023
|
+
ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
|
|
2024
|
+
ISSUE_TRACKER_PROVIDER_CUSTOM: "Benutzerdefiniert",
|
|
2025
|
+
ISSUE_TRACKER_FIELD_BASEURL: "Basis-URL",
|
|
2026
|
+
ISSUE_TRACKER_FIELD_ORG: "Organisation",
|
|
2027
|
+
ISSUE_TRACKER_FIELD_PROJECT: "Projekt",
|
|
2028
|
+
ISSUE_TRACKER_FIELD_OWNER: "Eigent\xFCmer",
|
|
2029
|
+
ISSUE_TRACKER_FIELD_REPO: "Repository",
|
|
2030
|
+
ISSUE_TRACKER_FIELD_TEMPLATE: "URL-Vorlage (muss {id} enthalten)",
|
|
2031
|
+
ISSUE_TRACKER_PH_JIRA_BASEURL: "https://ihre-org.atlassian.net",
|
|
2032
|
+
ISSUE_TRACKER_PH_AZURE_ORG: "Organisation",
|
|
2033
|
+
ISSUE_TRACKER_PH_AZURE_PROJECT: "Projekt",
|
|
2034
|
+
ISSUE_TRACKER_PH_GITHUB_OWNER: "Eigent\xFCmer",
|
|
2035
|
+
ISSUE_TRACKER_PH_GITHUB_REPO: "Repository",
|
|
2036
|
+
ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
|
|
1518
2037
|
},
|
|
1519
2038
|
SELECTOR_PICKER: {
|
|
1520
2039
|
TITLE: "DOM-Element ausw\xE4hlen",
|
|
@@ -1603,6 +2122,8 @@ var I18N_DE = {
|
|
|
1603
2122
|
FILE_EDITOR_TITLE: "\u270F\uFE0F Datei-Editor",
|
|
1604
2123
|
FILE_SAVED_TOAST: "\u2713 Datei gespeichert",
|
|
1605
2124
|
FILE_SAVE_ERROR_TOAST: "Fehler beim Speichern",
|
|
2125
|
+
FIXTURES_WRITTEN_TOAST: "\u2713 Fixtures in cypress/fixtures geschrieben",
|
|
2126
|
+
FIXTURES_NO_FOLDER_TOAST: "Fixtures konnten nicht geschrieben werden (Cypress-Ordner konfigurieren)",
|
|
1606
2127
|
BTN_CONFIG: "Config",
|
|
1607
2128
|
BTN_FILES: "Dateien",
|
|
1608
2129
|
BTN_COMMANDS: "Befehle",
|
|
@@ -1617,7 +2138,77 @@ var I18N_DE = {
|
|
|
1617
2138
|
SESSION_RESUME_TEXT: "Es gibt eine nicht abgeschlossene aktive Aufzeichnung.",
|
|
1618
2139
|
SESSION_RESUME_COUNT: "erfasste Befehle",
|
|
1619
2140
|
SESSION_CONTINUE_BTN: "Weiter aufzeichnen",
|
|
1620
|
-
SESSION_DISCARD_BTN: "Verwerfen"
|
|
2141
|
+
SESSION_DISCARD_BTN: "Verwerfen",
|
|
2142
|
+
BTN_HELP: "Hilfe"
|
|
2143
|
+
},
|
|
2144
|
+
HELP: {
|
|
2145
|
+
TITLE: "\u2753 Hilfe \u2014 Kurzanleitung",
|
|
2146
|
+
INTRO: "Alles, was der Recorder kann.",
|
|
2147
|
+
SEC_RECORDING: "\u23FA Aufzeichnen",
|
|
2148
|
+
RECORDING_1: "Dr\xFCcke \u23FA (oder Ctrl+R) zum Starten; \u23F9 zum Stoppen und Speichern.",
|
|
2149
|
+
RECORDING_2: "Pause / Fortsetzen mit \u23F8 (oder Ctrl+P).",
|
|
2150
|
+
SEC_SHORTCUTS: "\u2328\uFE0F Tastenk\xFCrzel",
|
|
2151
|
+
SC_RECORD: "Ctrl+R \u2014 Aufzeichnen / stoppen",
|
|
2152
|
+
SC_PAUSE: "Ctrl+P \u2014 Pausieren / fortsetzen",
|
|
2153
|
+
SC_TESTS: "Ctrl+1 \u2014 Panel gespeicherte Tests",
|
|
2154
|
+
SC_COMMANDS: "Ctrl+2 \u2014 Befehle-Panel",
|
|
2155
|
+
SC_CONFIG: "Ctrl+3 \u2014 Einstellungen",
|
|
2156
|
+
SC_VISIBILITY: "Ctrl+Shift+E \u2014 Widget ein-/ausblenden",
|
|
2157
|
+
SC_HELP: "Ctrl+Shift+H \u2014 Diese Hilfe",
|
|
2158
|
+
SEC_CAPTURED: "\u{1F3AF} Was aufgezeichnet wird",
|
|
2159
|
+
CAP_CLICK: "Klick \u2192 .click()",
|
|
2160
|
+
CAP_DBLCLICK: "Doppelklick \u2192 .dblclick()",
|
|
2161
|
+
CAP_RIGHTCLICK: "Rechtsklick \u2192 .rightclick()",
|
|
2162
|
+
CAP_TYPE: "In ein Feld tippen \u2192 .clear().type(...)",
|
|
2163
|
+
CAP_CHECK: "Checkbox / Radio \u2192 .check() / .uncheck()",
|
|
2164
|
+
CAP_SELECT: "<select> \u2192 .select(...)",
|
|
2165
|
+
CAP_KEYS: 'Enter / Escape \u2192 .type("{enter}") / ("{esc}")',
|
|
2166
|
+
CAP_ROUTE: "Routenwechsel (SPA) \u2192 cy.url().should(...)",
|
|
2167
|
+
CAP_HTTP: "fetch / XHR \u2192 cy.intercept + cy.wait",
|
|
2168
|
+
SEC_ASSERT: "\u2705 Assertions",
|
|
2169
|
+
ASSERT_1: "Alt+Klick auf ein Element, um es zu pr\xFCfen, ohne seine Aktion auszul\xF6sen (Text, Wert, checked oder sichtbar).",
|
|
2170
|
+
ASSERT_2: "Oder nutze den Assertion-Builder im Befehle-Panel.",
|
|
2171
|
+
SEC_PANELS: "\u{1F5C2} Panels",
|
|
2172
|
+
PANEL_TESTS: "\u{1F4CB} Tests \u2014 gespeichert: nach Tag filtern, kopieren, kombinieren, exportieren.",
|
|
2173
|
+
PANEL_COMMANDS: "\u2328\uFE0F Befehle \u2014 aktuelle Aufzeichnung: umsortieren, l\xF6schen, Assertions hinzuf\xFCgen.",
|
|
2174
|
+
PANEL_CONFIG: "\u2699\uFE0F Config \u2014 Sprache, Selektor, HTTP, Cypress-Ordner, Daten, Sichtbarkeit, Position.",
|
|
2175
|
+
PANEL_FILES: "\u{1F4C1} Dateien \u2014 Tests in deine .cy.ts einf\xFCgen und den Runner starten.",
|
|
2176
|
+
SEC_SELECTOR: "\u{1F50E} Selektoren",
|
|
2177
|
+
SELECTOR_1: "W\xE4hle das bevorzugte Attribut (data-cy, data-testid, aria-label, id) unter Config.",
|
|
2178
|
+
SELECTOR_2: "Hat ein Klick keinen zuverl\xE4ssigen Selektor, erscheint ein Picker zur Wahl des besten Vorfahren.",
|
|
2179
|
+
SEC_DATA: "\u{1F4BE} Daten",
|
|
2180
|
+
DATA_1: "Tests als JSON exportieren (alle / Auswahl / nach Tags).",
|
|
2181
|
+
DATA_2: "JSON importieren: wird zu deinen Daten hinzugef\xFCgt (nichts wird gel\xF6scht).",
|
|
2182
|
+
SEC_ADVANCED: "\u{1F680} Erweitert",
|
|
2183
|
+
ADV_INVISIBLE: "Unsichtbarer Modus: start-hidden + Ctrl+Shift+E, um in Produktion unauff\xE4llig aufzuzeichnen.",
|
|
2184
|
+
ADV_RUNNER: "Lokaler Runner: npx lib-e2e-cypress-runner, um einen Spec headless aus dem Editor zu starten.",
|
|
2185
|
+
ADV_CROSSAPP: "Cross-App: die Aufzeichnung \xFCbersteht den Wechsel des Micro-Frontends (gleicher Origin).",
|
|
2186
|
+
ADV_DRAG: "Ziehe den \u23FA-Button, um das Widget zu verschieben; die Position wird gemerkt.",
|
|
2187
|
+
CLOSE: "Schlie\xDFen",
|
|
2188
|
+
TAB_REFERENCE: "Kurzreferenz",
|
|
2189
|
+
TAB_GUIDE: "Nutzungsleitfaden",
|
|
2190
|
+
// TODO: translate (de) — usage guide body kept in Spanish for now.
|
|
2191
|
+
GUIDE_INTRO: "C\xF3mo sacarle partido al grabador, qu\xE9 cubre y qu\xE9 no.",
|
|
2192
|
+
G_SEC_WORKFLOW: "\u{1F9ED} Flujo de trabajo",
|
|
2193
|
+
G_WF_1: "1. Pulsa \u23FA (o Ctrl+R) para empezar a grabar.",
|
|
2194
|
+
G_WF_2: "2. Usa tu app como un usuario: clicks, formularios, navegaci\xF3n. Cada acci\xF3n se convierte en un comando Cypress.",
|
|
2195
|
+
G_WF_3: "3. A\xF1ade verificaciones con Alt+click sobre lo que quieras comprobar.",
|
|
2196
|
+
G_WF_4: "4. Pulsa \u23F9 para parar; ponle nombre y tags, y gu\xE1rdalo.",
|
|
2197
|
+
G_WF_5: "5. En \u{1F4C1} Ficheros, inserta el test en un .cy.ts o l\xE1nzalo con el runner local.",
|
|
2198
|
+
G_SEC_COVERED: "\u2705 Qu\xE9 cubre",
|
|
2199
|
+
G_COV_1: "Clicks, doble-click, click derecho, inputs de texto, checkbox/radio, select y teclas Enter/Escape.",
|
|
2200
|
+
G_COV_2: "Navegaci\xF3n SPA (pushState/replaceState/popstate) \u2192 cy.url().should(...).",
|
|
2201
|
+
G_COV_3: "Llamadas fetch/XHR (GET/POST/PUT) \u2192 cy.intercept + cy.wait, con validaci\xF3n de body opcional.",
|
|
2202
|
+
G_COV_4: "Selector fiable (data-cy/testid/aria/id) y picker cuando no hay uno claro.",
|
|
2203
|
+
G_COV_5: "Aserciones autom\xE1ticas (Alt+click) y constructor manual.",
|
|
2204
|
+
G_COV_6: "Continuidad al cambiar de micro-frontend (mismo origen), modo invisible e i18n.",
|
|
2205
|
+
G_SEC_NOTCOVERED: "\u{1F6AB} Qu\xE9 NO cubre (por ahora)",
|
|
2206
|
+
G_NC_1: "Or\xEDgenes distintos / subdominios (solo mismo origen).",
|
|
2207
|
+
G_NC_2: "Subida de ficheros (selectFile), arrastrar y soltar, y hover.",
|
|
2208
|
+
G_NC_3: "La tecla Tab (Cypress no la soporta en .type()).",
|
|
2209
|
+
G_NC_4: "Peticiones DELETE (se ignoran a prop\xF3sito).",
|
|
2210
|
+
G_NC_5: "iframes y Shadow DOM de terceros pueden no capturarse.",
|
|
2211
|
+
G_NC_6: "Generaci\xF3n de login program\xE1tico (en el roadmap)."
|
|
1621
2212
|
}
|
|
1622
2213
|
};
|
|
1623
2214
|
|
|
@@ -1750,6 +2341,32 @@ var advancedTestTransformationService = new AdvancedTestTransformationService();
|
|
|
1750
2341
|
|
|
1751
2342
|
// src/services/recording.service.ts
|
|
1752
2343
|
init_selector_quality_utils();
|
|
2344
|
+
|
|
2345
|
+
// src/utils/assertion.utils.ts
|
|
2346
|
+
var MAX_TEXT_LENGTH = 60;
|
|
2347
|
+
function inferAssertionCommand(el, selector) {
|
|
2348
|
+
const should = (body) => `cy.get('${selector}').should(${body})`;
|
|
2349
|
+
const tag = el.tagName.toLowerCase();
|
|
2350
|
+
if (tag === "input") {
|
|
2351
|
+
const input = el;
|
|
2352
|
+
const type = (input.getAttribute("type") ?? "text").toLowerCase();
|
|
2353
|
+
if (type === "checkbox" || type === "radio") {
|
|
2354
|
+
return should(`'${input.checked ? "be.checked" : "not.be.checked"}'`);
|
|
2355
|
+
}
|
|
2356
|
+
return input.value ? should(`'have.value', '${escapeSingleQuotes(input.value)}'`) : should(`'be.visible'`);
|
|
2357
|
+
}
|
|
2358
|
+
if (tag === "textarea" || tag === "select") {
|
|
2359
|
+
const value = el.value;
|
|
2360
|
+
return value ? should(`'have.value', '${escapeSingleQuotes(value)}'`) : should(`'be.visible'`);
|
|
2361
|
+
}
|
|
2362
|
+
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
2363
|
+
if (text && text.length <= MAX_TEXT_LENGTH) {
|
|
2364
|
+
return should(`'contain.text', '${escapeSingleQuotes(text)}'`);
|
|
2365
|
+
}
|
|
2366
|
+
return should(`'be.visible'`);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// src/services/recording.service.ts
|
|
1753
2370
|
var OWN_SELECTOR = '[data-cy="lib-e2e-cypress-for-dummys"]';
|
|
1754
2371
|
function createSessionId() {
|
|
1755
2372
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
@@ -1764,6 +2381,7 @@ var RecordingService = class {
|
|
|
1764
2381
|
isPaused$ = new Subject(false);
|
|
1765
2382
|
selectorNotFound$ = new Subject(null);
|
|
1766
2383
|
inputDebounceTimers = /* @__PURE__ */ new Map();
|
|
2384
|
+
fixtures = /* @__PURE__ */ new Map();
|
|
1767
2385
|
abort = new AbortController();
|
|
1768
2386
|
selectorStrategy = "data-cy";
|
|
1769
2387
|
/** Stable id of the current live session (null when none has started). */
|
|
@@ -1773,9 +2391,13 @@ var RecordingService = class {
|
|
|
1773
2391
|
origPushState = history.pushState.bind(history);
|
|
1774
2392
|
origReplaceState = history.replaceState.bind(history);
|
|
1775
2393
|
constructor() {
|
|
2394
|
+
this.listenToAssertClicks();
|
|
1776
2395
|
this.listenToClicks();
|
|
2396
|
+
this.listenToDoubleClicks();
|
|
2397
|
+
this.listenToContextMenu();
|
|
1777
2398
|
this.listenToInput();
|
|
1778
2399
|
this.listenToSelect();
|
|
2400
|
+
this.listenToKeys();
|
|
1779
2401
|
this.listenToRouteChanges();
|
|
1780
2402
|
}
|
|
1781
2403
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
@@ -1866,17 +2488,27 @@ var RecordingService = class {
|
|
|
1866
2488
|
if (index < 0 || index >= ints.length) return;
|
|
1867
2489
|
this.interceptors$.next([...ints.slice(0, index), ...ints.slice(index + 1)]);
|
|
1868
2490
|
}
|
|
1869
|
-
registerInterceptor(method, url, alias) {
|
|
2491
|
+
registerInterceptor(method, url, alias, fixtureFile) {
|
|
1870
2492
|
if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
|
|
1871
|
-
const
|
|
2493
|
+
const wildcard = this.urlToWildcard(url, method);
|
|
2494
|
+
const command = fixtureFile ? `cy.intercept('${method}', '${wildcard}', { fixture: '${fixtureFile}' }).as('${alias}')` : `cy.intercept('${method}', '${wildcard}').as('${alias}')`;
|
|
1872
2495
|
const current = this.interceptors$.getValue();
|
|
1873
2496
|
if (!current.includes(command)) {
|
|
1874
2497
|
this.interceptors$.next([...current, command]);
|
|
1875
2498
|
}
|
|
1876
2499
|
}
|
|
2500
|
+
/** Registers a captured response as a Cypress fixture (name → JSON content). */
|
|
2501
|
+
registerFixture(name, content) {
|
|
2502
|
+
if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
|
|
2503
|
+
this.fixtures.set(name, content);
|
|
2504
|
+
}
|
|
2505
|
+
getFixturesSnapshot() {
|
|
2506
|
+
return [...this.fixtures.entries()].map(([name, content]) => ({ name, content }));
|
|
2507
|
+
}
|
|
1877
2508
|
clearCommands() {
|
|
1878
2509
|
this.commands$.next([]);
|
|
1879
2510
|
this.interceptors$.next([]);
|
|
2511
|
+
this.fixtures.clear();
|
|
1880
2512
|
}
|
|
1881
2513
|
clearInterceptors() {
|
|
1882
2514
|
this.interceptors$.next([]);
|
|
@@ -1930,6 +2562,39 @@ var RecordingService = class {
|
|
|
1930
2562
|
this.inputDebounceTimers.clear();
|
|
1931
2563
|
}
|
|
1932
2564
|
// ── DOM listeners ─────────────────────────────────────────────────────────
|
|
2565
|
+
/**
|
|
2566
|
+
* Alt+click captures an ASSERTION for the element instead of a click, and
|
|
2567
|
+
* suppresses the element's real action. Runs in the capture phase so it fires
|
|
2568
|
+
* before the app's handlers and before the bubble-phase click listener below
|
|
2569
|
+
* (stopImmediatePropagation prevents a duplicate `.click()`). See spec 009.
|
|
2570
|
+
*/
|
|
2571
|
+
listenToAssertClicks() {
|
|
2572
|
+
document.addEventListener(
|
|
2573
|
+
"click",
|
|
2574
|
+
(e) => {
|
|
2575
|
+
if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
|
|
2576
|
+
if (!e.altKey) return;
|
|
2577
|
+
const target = e.target;
|
|
2578
|
+
if (!target || this.isOwnElement(target)) return;
|
|
2579
|
+
const tag = target.tagName?.toLowerCase();
|
|
2580
|
+
if (tag === "body" || tag === "html") return;
|
|
2581
|
+
const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
|
|
2582
|
+
if (!container) return;
|
|
2583
|
+
const selector = this.getReliableSelector(container);
|
|
2584
|
+
if (!selector || this.isOwnSelector(selector)) return;
|
|
2585
|
+
e.preventDefault();
|
|
2586
|
+
e.stopImmediatePropagation();
|
|
2587
|
+
const emit = () => this.addCommand(inferAssertionCommand(container, selector));
|
|
2588
|
+
const type = (container.getAttribute("type") ?? "").toLowerCase();
|
|
2589
|
+
if (container.tagName.toLowerCase() === "input" && (type === "checkbox" || type === "radio")) {
|
|
2590
|
+
queueMicrotask(emit);
|
|
2591
|
+
} else {
|
|
2592
|
+
emit();
|
|
2593
|
+
}
|
|
2594
|
+
},
|
|
2595
|
+
{ capture: true, signal: this.abort.signal }
|
|
2596
|
+
);
|
|
2597
|
+
}
|
|
1933
2598
|
listenToClicks() {
|
|
1934
2599
|
document.addEventListener(
|
|
1935
2600
|
"click",
|
|
@@ -1944,6 +2609,70 @@ var RecordingService = class {
|
|
|
1944
2609
|
{ signal: this.abort.signal }
|
|
1945
2610
|
);
|
|
1946
2611
|
}
|
|
2612
|
+
/** Centralised selector resolution for the pointer/key listeners (spec 010). */
|
|
2613
|
+
resolveSelectorFor(target) {
|
|
2614
|
+
if (!target || this.isOwnElement(target)) return null;
|
|
2615
|
+
const tag = target.tagName?.toLowerCase();
|
|
2616
|
+
if (tag === "body" || tag === "html") return null;
|
|
2617
|
+
const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
|
|
2618
|
+
if (!container) return null;
|
|
2619
|
+
const selector = this.getReliableSelector(container);
|
|
2620
|
+
if (!selector || this.isOwnSelector(selector)) return null;
|
|
2621
|
+
return selector;
|
|
2622
|
+
}
|
|
2623
|
+
listenToDoubleClicks() {
|
|
2624
|
+
document.addEventListener(
|
|
2625
|
+
"dblclick",
|
|
2626
|
+
(e) => {
|
|
2627
|
+
if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
|
|
2628
|
+
if (e.altKey) return;
|
|
2629
|
+
const selector = this.resolveSelectorFor(e.target);
|
|
2630
|
+
if (!selector) return;
|
|
2631
|
+
const clickCmd = `cy.get('${selector}').click()`;
|
|
2632
|
+
let cmds = this.commands$.getValue();
|
|
2633
|
+
let removed = 0;
|
|
2634
|
+
while (removed < 2 && cmds.length > 0 && cmds[cmds.length - 1] === clickCmd) {
|
|
2635
|
+
cmds = cmds.slice(0, -1);
|
|
2636
|
+
removed++;
|
|
2637
|
+
}
|
|
2638
|
+
if (removed > 0) this.commands$.next(cmds);
|
|
2639
|
+
this.addCommand(`cy.get('${selector}').dblclick()`);
|
|
2640
|
+
},
|
|
2641
|
+
{ signal: this.abort.signal }
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
listenToContextMenu() {
|
|
2645
|
+
document.addEventListener(
|
|
2646
|
+
"contextmenu",
|
|
2647
|
+
(e) => {
|
|
2648
|
+
if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
|
|
2649
|
+
const selector = this.resolveSelectorFor(e.target);
|
|
2650
|
+
if (!selector) return;
|
|
2651
|
+
this.addCommand(`cy.get('${selector}').rightclick()`);
|
|
2652
|
+
},
|
|
2653
|
+
{ signal: this.abort.signal }
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
/** Enter → type('{enter}'), Escape → type('{esc}'), only inside a field. */
|
|
2657
|
+
listenToKeys() {
|
|
2658
|
+
document.addEventListener(
|
|
2659
|
+
"keydown",
|
|
2660
|
+
(e) => {
|
|
2661
|
+
if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
|
|
2662
|
+
const key = e.key;
|
|
2663
|
+
if (key !== "Enter" && key !== "Escape") return;
|
|
2664
|
+
const target = e.target;
|
|
2665
|
+
const tag = target?.tagName?.toLowerCase();
|
|
2666
|
+
if (tag !== "input" && tag !== "textarea" && tag !== "select") return;
|
|
2667
|
+
const selector = this.resolveSelectorFor(target);
|
|
2668
|
+
if (!selector) return;
|
|
2669
|
+
this.flushInputDebounce(target);
|
|
2670
|
+
const token = key === "Enter" ? "{enter}" : "{esc}";
|
|
2671
|
+
this.addCommand(`cy.get('${selector}').type('${token}')`);
|
|
2672
|
+
},
|
|
2673
|
+
{ signal: this.abort.signal }
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
1947
2676
|
listenToInput() {
|
|
1948
2677
|
document.addEventListener(
|
|
1949
2678
|
"input",
|
|
@@ -2026,7 +2755,21 @@ var RecordingService = class {
|
|
|
2026
2755
|
const tag = target.tagName.toLowerCase();
|
|
2027
2756
|
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
2028
2757
|
const container2 = target.closest("[data-cy], [data-testid], [aria-label], [id]");
|
|
2029
|
-
if (!container2)
|
|
2758
|
+
if (!container2) {
|
|
2759
|
+
this.selectorNotFound$.next({ target, action: "click" });
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
if (tag === "input") {
|
|
2763
|
+
const input = target;
|
|
2764
|
+
const type = (input.getAttribute("type") ?? "").toLowerCase();
|
|
2765
|
+
if (type === "checkbox" || type === "radio") {
|
|
2766
|
+
const selector2 = this.getReliableSelector(container2);
|
|
2767
|
+
if (selector2 && !this.isOwnSelector(selector2)) {
|
|
2768
|
+
const action = type === "radio" || input.checked ? "check" : "uncheck";
|
|
2769
|
+
this.addCommand(`cy.get('${selector2}').${action}()`);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2030
2773
|
return;
|
|
2031
2774
|
}
|
|
2032
2775
|
const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
|
|
@@ -2073,16 +2816,30 @@ var RecordingService = class {
|
|
|
2073
2816
|
this.inputDebounceTimers.set(
|
|
2074
2817
|
target,
|
|
2075
2818
|
setTimeout(() => {
|
|
2076
|
-
const selector = this.getReliableSelector(container);
|
|
2077
|
-
const value = target.value.replace(/'/g, "\\'");
|
|
2078
|
-
this.addGenericCommand({
|
|
2079
|
-
selector,
|
|
2080
|
-
action: (s) => `cy.get('${s}').clear().type('${value}')`
|
|
2081
|
-
});
|
|
2082
2819
|
this.inputDebounceTimers.delete(target);
|
|
2820
|
+
this.recordInputValue(target);
|
|
2083
2821
|
}, 1e3)
|
|
2084
2822
|
);
|
|
2085
2823
|
}
|
|
2824
|
+
/** Records a text field's current value as a `clear().type()` command. */
|
|
2825
|
+
recordInputValue(target) {
|
|
2826
|
+
const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
|
|
2827
|
+
if (!container) return;
|
|
2828
|
+
const selector = this.getReliableSelector(container);
|
|
2829
|
+
const value = target.value.replace(/'/g, "\\'");
|
|
2830
|
+
this.addGenericCommand({
|
|
2831
|
+
selector,
|
|
2832
|
+
action: (s) => `cy.get('${s}').clear().type('${value}')`
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
/** Flushes any pending debounced value for an element, recording it now. */
|
|
2836
|
+
flushInputDebounce(target) {
|
|
2837
|
+
const timer = this.inputDebounceTimers.get(target);
|
|
2838
|
+
if (timer === void 0) return;
|
|
2839
|
+
clearTimeout(timer);
|
|
2840
|
+
this.inputDebounceTimers.delete(target);
|
|
2841
|
+
this.recordInputValue(target);
|
|
2842
|
+
}
|
|
2086
2843
|
// ── Select helpers ────────────────────────────────────────────────────────
|
|
2087
2844
|
handleSelectEvent(target) {
|
|
2088
2845
|
const container = target.closest("[data-cy], [data-testid], [aria-label], [id]");
|
|
@@ -2148,6 +2905,42 @@ var RecordingService = class {
|
|
|
2148
2905
|
|
|
2149
2906
|
// src/services/persistence.service.ts
|
|
2150
2907
|
var import_idb = require("idb");
|
|
2908
|
+
|
|
2909
|
+
// src/utils/ticket.utils.ts
|
|
2910
|
+
function buildTicketUrl(provider, params, id) {
|
|
2911
|
+
const ticketId = (id ?? "").trim();
|
|
2912
|
+
if (!provider || !ticketId) return null;
|
|
2913
|
+
let url = provider.urlTemplate;
|
|
2914
|
+
for (const field of provider.fields) {
|
|
2915
|
+
const value = (params?.[field.key] ?? "").trim().replace(/\/+$/, "");
|
|
2916
|
+
if (!value) return null;
|
|
2917
|
+
url = url.split(`{${field.key}}`).join(value);
|
|
2918
|
+
}
|
|
2919
|
+
url = url.split("{id}").join(encodeURIComponent(ticketId));
|
|
2920
|
+
if (url.includes("{") || url.includes("}")) return null;
|
|
2921
|
+
return toSafeHttpUrl(url);
|
|
2922
|
+
}
|
|
2923
|
+
function resolveTicketUrl(providerId, allParams, ticketId) {
|
|
2924
|
+
const id = (ticketId ?? "").trim();
|
|
2925
|
+
if (!id) return null;
|
|
2926
|
+
const provider = findIssueTrackerProvider(providerId);
|
|
2927
|
+
if (!provider) return null;
|
|
2928
|
+
return buildTicketUrl(provider, allParams?.[providerId] ?? {}, id);
|
|
2929
|
+
}
|
|
2930
|
+
function toSafeHttpUrl(candidate) {
|
|
2931
|
+
try {
|
|
2932
|
+
const parsed = new URL(candidate);
|
|
2933
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : null;
|
|
2934
|
+
} catch {
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
function buildTicketComment(id, url) {
|
|
2939
|
+
const ticketId = (id ?? "").trim();
|
|
2940
|
+
return url ? `// ${ticketId} \u2014 ${url}` : `// ${ticketId}`;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
// src/services/persistence.service.ts
|
|
2151
2944
|
var ACTIVE_SESSION_ID = 1;
|
|
2152
2945
|
var PersistenceService = class {
|
|
2153
2946
|
// dbName is overridable so tests can use unique names for isolation.
|
|
@@ -2177,9 +2970,9 @@ var PersistenceService = class {
|
|
|
2177
2970
|
return this._db;
|
|
2178
2971
|
}
|
|
2179
2972
|
// ── Tests ─────────────────────────────────────────────────────────────────
|
|
2180
|
-
async insertTest(name, commands = [], interceptors = [], tags = [], notes) {
|
|
2973
|
+
async insertTest(name, commands = [], interceptors = [], tags = [], notes, ticketId) {
|
|
2181
2974
|
const db = await this.getDB();
|
|
2182
|
-
const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {}, ...notes ? { notes } : {} };
|
|
2975
|
+
const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {}, ...notes ? { notes } : {}, ...ticketId ? { ticketId } : {} };
|
|
2183
2976
|
const id = await db.add("tests", record);
|
|
2184
2977
|
if (commands.length) await this.insertCommands(commands, id);
|
|
2185
2978
|
if (interceptors.length) await this.insertInterceptors(interceptors, id);
|
|
@@ -2202,12 +2995,28 @@ var PersistenceService = class {
|
|
|
2202
2995
|
if (!record) return null;
|
|
2203
2996
|
const commands = await this.getCommandStrings(testId);
|
|
2204
2997
|
const interceptors = await this.getInterceptorStrings(testId);
|
|
2205
|
-
const
|
|
2998
|
+
const ticketComment = await this.ticketCommentFor(record);
|
|
2999
|
+
const itBody = `it('${escapeSingleQuotes(record.name)}', () => {
|
|
2206
3000
|
${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
2207
3001
|
});`;
|
|
3002
|
+
const itBlock = ticketComment ? `${ticketComment}
|
|
3003
|
+
${itBody}` : itBody;
|
|
2208
3004
|
const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
|
|
2209
3005
|
return { ...record, commands, interceptors, cypressCommands: commands, itBlock, interceptorsBlock };
|
|
2210
3006
|
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Builds the traceability comment for a test record (spec 014), reading the
|
|
3009
|
+
* configured issue-tracker provider/params so the link — when resolvable — is
|
|
3010
|
+
* embedded. Returns `null` when the record has no ticket id.
|
|
3011
|
+
*/
|
|
3012
|
+
async ticketCommentFor(record) {
|
|
3013
|
+
if (!record.ticketId) return null;
|
|
3014
|
+
const config = await this.getGeneralConfig();
|
|
3015
|
+
const providerId = config?.["issueTrackerProviderId"] ?? DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
|
|
3016
|
+
const params = config?.["issueTrackerParams"] ?? {};
|
|
3017
|
+
const url = resolveTicketUrl(providerId, params, record.ticketId);
|
|
3018
|
+
return buildTicketComment(record.ticketId, url);
|
|
3019
|
+
}
|
|
2211
3020
|
async deleteTest(id) {
|
|
2212
3021
|
const db = await this.getDB();
|
|
2213
3022
|
await db.delete("tests", id);
|
|
@@ -2333,6 +3142,31 @@ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
2333
3142
|
await this.setConfigKey("cypressDirectoryHandle", dirHandle);
|
|
2334
3143
|
await this.setConfigKey("allowReadWriteFiles", "true");
|
|
2335
3144
|
}
|
|
3145
|
+
/**
|
|
3146
|
+
* Writes captured fixtures into `cypress/fixtures/` using the configured Cypress
|
|
3147
|
+
* folder handle (spec 012). Returns the number written. Throws if no folder is
|
|
3148
|
+
* configured or write permission is denied.
|
|
3149
|
+
*/
|
|
3150
|
+
async writeFixtures(fixtures) {
|
|
3151
|
+
if (!fixtures.length) return 0;
|
|
3152
|
+
const config = await this.getGeneralConfig();
|
|
3153
|
+
const dirHandle = config?.["cypressDirectoryHandle"];
|
|
3154
|
+
if (!dirHandle) throw new Error("No Cypress folder configured");
|
|
3155
|
+
const perm = dirHandle;
|
|
3156
|
+
let state = await perm.queryPermission({ mode: "readwrite" });
|
|
3157
|
+
if (state !== "granted") state = await perm.requestPermission({ mode: "readwrite" });
|
|
3158
|
+
if (state !== "granted") throw new Error("Write permission denied");
|
|
3159
|
+
const fixturesDir = await dirHandle.getDirectoryHandle("fixtures", { create: true });
|
|
3160
|
+
let written = 0;
|
|
3161
|
+
for (const fx of fixtures) {
|
|
3162
|
+
const fileHandle = await fixturesDir.getFileHandle(fx.name, { create: true });
|
|
3163
|
+
const writable = await fileHandle.createWritable();
|
|
3164
|
+
await writable.write(fx.content);
|
|
3165
|
+
await writable.close();
|
|
3166
|
+
written++;
|
|
3167
|
+
}
|
|
3168
|
+
return written;
|
|
3169
|
+
}
|
|
2336
3170
|
async bulkInsertWithoutId(store, items) {
|
|
2337
3171
|
if (!Array.isArray(items)) return;
|
|
2338
3172
|
const db = await this.getDB();
|
|
@@ -2385,6 +3219,14 @@ function parseJsonObject(text) {
|
|
|
2385
3219
|
}
|
|
2386
3220
|
return null;
|
|
2387
3221
|
}
|
|
3222
|
+
function prettyJsonOrNull(text) {
|
|
3223
|
+
try {
|
|
3224
|
+
const parsed = JSON.parse(text);
|
|
3225
|
+
if (parsed && typeof parsed === "object") return JSON.stringify(parsed, null, 2);
|
|
3226
|
+
} catch {
|
|
3227
|
+
}
|
|
3228
|
+
return null;
|
|
3229
|
+
}
|
|
2388
3230
|
function parseRequestBody(init) {
|
|
2389
3231
|
const body = init?.body;
|
|
2390
3232
|
if (typeof body === "string") return parseJsonObject(body);
|
|
@@ -2418,6 +3260,9 @@ var HttpMonitor = class {
|
|
|
2418
3260
|
isExtendedHttpEnabled() {
|
|
2419
3261
|
return localStorage.getItem("extendedHttpCommands") === "true";
|
|
2420
3262
|
}
|
|
3263
|
+
isFixtureModeEnabled() {
|
|
3264
|
+
return localStorage.getItem("fixtureMode") === "true";
|
|
3265
|
+
}
|
|
2421
3266
|
installFetch() {
|
|
2422
3267
|
if (this.originalFetch) return;
|
|
2423
3268
|
const originalFetch = window.fetch;
|
|
@@ -2479,6 +3324,14 @@ var HttpMonitor = class {
|
|
|
2479
3324
|
if (!INTERCEPTED_METHODS.includes(method)) return;
|
|
2480
3325
|
const url = resolveUrl(input);
|
|
2481
3326
|
const alias = generateAlias(method, url);
|
|
3327
|
+
if (this.isFixtureModeEnabled() && method === "GET") {
|
|
3328
|
+
const pretty = prettyJsonOrNull(await responseClone.text());
|
|
3329
|
+
const fixtureFile = `${alias}.json`;
|
|
3330
|
+
if (pretty !== null) this.recording.registerFixture(fixtureFile, pretty);
|
|
3331
|
+
this.recording.registerInterceptor(method, url, alias, pretty !== null ? fixtureFile : void 0);
|
|
3332
|
+
this.recording.addCommand(`cy.wait('@${alias}').then((interception) => { })`);
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
2482
3335
|
this.recording.registerInterceptor(method, url, alias);
|
|
2483
3336
|
const extendedHttp = this.isExtendedHttpEnabled();
|
|
2484
3337
|
const requestBody = extendedHttp ? parseRequestBody(init) : null;
|
|
@@ -2495,6 +3348,14 @@ var HttpMonitor = class {
|
|
|
2495
3348
|
handleXhrInterception(method, url, requestBody, responseText) {
|
|
2496
3349
|
if (!INTERCEPTED_METHODS.includes(method)) return;
|
|
2497
3350
|
const alias = generateAlias(method, url);
|
|
3351
|
+
if (this.isFixtureModeEnabled() && method === "GET") {
|
|
3352
|
+
const pretty = prettyJsonOrNull(responseText);
|
|
3353
|
+
const fixtureFile = `${alias}.json`;
|
|
3354
|
+
if (pretty !== null) this.recording.registerFixture(fixtureFile, pretty);
|
|
3355
|
+
this.recording.registerInterceptor(method, url, alias, pretty !== null ? fixtureFile : void 0);
|
|
3356
|
+
this.recording.addCommand(`cy.wait('@${alias}').then((interception) => { })`);
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
2498
3359
|
this.recording.registerInterceptor(method, url, alias);
|
|
2499
3360
|
const extendedHttp = this.isExtendedHttpEnabled();
|
|
2500
3361
|
const responseBody = extendedHttp ? parseJsonObject(responseText) : null;
|
|
@@ -2613,6 +3474,10 @@ var LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES = `
|
|
|
2613
3474
|
align-items: stretch;
|
|
2614
3475
|
box-sizing: border-box;
|
|
2615
3476
|
overflow: hidden !important;
|
|
3477
|
+
/* Left-align modal body text/code (SweetAlert defaults it to centre, which
|
|
3478
|
+
inherits into the panels \u2014 including their shadow DOM). Titles/buttons keep
|
|
3479
|
+
their own alignment. */
|
|
3480
|
+
text-align: left !important;
|
|
2616
3481
|
}
|
|
2617
3482
|
/* The single wrapper div we inject inside each modal fills the container */
|
|
2618
3483
|
.swal2-html-container > div {
|
|
@@ -2816,6 +3681,177 @@ function boxTopLeftFor(toggleCentre, dir) {
|
|
|
2816
3681
|
return { x: toggleCentre.x - off.x, y: toggleCentre.y - off.y };
|
|
2817
3682
|
}
|
|
2818
3683
|
|
|
3684
|
+
// src/utils/cypress-completions.ts
|
|
3685
|
+
var CY_METHODS = [
|
|
3686
|
+
"visit",
|
|
3687
|
+
"get",
|
|
3688
|
+
"contains",
|
|
3689
|
+
"intercept",
|
|
3690
|
+
"wait",
|
|
3691
|
+
"request",
|
|
3692
|
+
"url",
|
|
3693
|
+
"location",
|
|
3694
|
+
"viewport",
|
|
3695
|
+
"fixture",
|
|
3696
|
+
"wrap",
|
|
3697
|
+
"reload",
|
|
3698
|
+
"go",
|
|
3699
|
+
"window",
|
|
3700
|
+
"document",
|
|
3701
|
+
"session",
|
|
3702
|
+
"clearCookies",
|
|
3703
|
+
"clearLocalStorage",
|
|
3704
|
+
"screenshot",
|
|
3705
|
+
"log"
|
|
3706
|
+
];
|
|
3707
|
+
var CHAIN_METHODS = [
|
|
3708
|
+
"click",
|
|
3709
|
+
"dblclick",
|
|
3710
|
+
"rightclick",
|
|
3711
|
+
"type",
|
|
3712
|
+
"clear",
|
|
3713
|
+
"select",
|
|
3714
|
+
"check",
|
|
3715
|
+
"uncheck",
|
|
3716
|
+
"should",
|
|
3717
|
+
"and",
|
|
3718
|
+
"find",
|
|
3719
|
+
"first",
|
|
3720
|
+
"last",
|
|
3721
|
+
"eq",
|
|
3722
|
+
"then",
|
|
3723
|
+
"its",
|
|
3724
|
+
"invoke",
|
|
3725
|
+
"as",
|
|
3726
|
+
"trigger",
|
|
3727
|
+
"focus",
|
|
3728
|
+
"blur",
|
|
3729
|
+
"scrollIntoView",
|
|
3730
|
+
"within",
|
|
3731
|
+
"parent",
|
|
3732
|
+
"children",
|
|
3733
|
+
"siblings",
|
|
3734
|
+
"contains",
|
|
3735
|
+
"each",
|
|
3736
|
+
"filter",
|
|
3737
|
+
"wait"
|
|
3738
|
+
];
|
|
3739
|
+
var TOP_LEVEL = [
|
|
3740
|
+
"cy",
|
|
3741
|
+
"describe",
|
|
3742
|
+
"it",
|
|
3743
|
+
"context",
|
|
3744
|
+
"beforeEach",
|
|
3745
|
+
"afterEach",
|
|
3746
|
+
"before",
|
|
3747
|
+
"after",
|
|
3748
|
+
"Cypress"
|
|
3749
|
+
];
|
|
3750
|
+
var CYPRESS_COMPLETIONS = [
|
|
3751
|
+
...TOP_LEVEL,
|
|
3752
|
+
...CY_METHODS.map((m) => `cy.${m}`),
|
|
3753
|
+
...CHAIN_METHODS
|
|
3754
|
+
];
|
|
3755
|
+
function filterCompletions(list, prefix) {
|
|
3756
|
+
const p = prefix.toLowerCase();
|
|
3757
|
+
if (!p) return [...list];
|
|
3758
|
+
return list.filter((label) => label.toLowerCase().includes(p));
|
|
3759
|
+
}
|
|
3760
|
+
function filterCypressCompletions(prefix) {
|
|
3761
|
+
return filterCompletions(CYPRESS_COMPLETIONS, prefix);
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
// src/utils/code-editor.ts
|
|
3765
|
+
function toOptions(labels, type) {
|
|
3766
|
+
return labels.map((label) => ({ label, type }));
|
|
3767
|
+
}
|
|
3768
|
+
function cypressCompletionSource(context) {
|
|
3769
|
+
const cyMatch = context.matchBefore(/cy\.\w*$/);
|
|
3770
|
+
if (cyMatch) {
|
|
3771
|
+
return { from: cyMatch.from + 3, options: toOptions(filterCompletions(CY_METHODS, cyMatch.text.slice(3)), "method") };
|
|
3772
|
+
}
|
|
3773
|
+
const chainMatch = context.matchBefore(/\.\w*$/);
|
|
3774
|
+
if (chainMatch) {
|
|
3775
|
+
return { from: chainMatch.from + 1, options: toOptions(filterCompletions(CHAIN_METHODS, chainMatch.text.slice(1)), "method") };
|
|
3776
|
+
}
|
|
3777
|
+
const word = context.matchBefore(/\w*/);
|
|
3778
|
+
if (!word || word.from === word.to && !context.explicit) return null;
|
|
3779
|
+
return { from: word.from, options: toOptions(filterCompletions(TOP_LEVEL, word.text), "keyword") };
|
|
3780
|
+
}
|
|
3781
|
+
async function createCodeEditor(opts) {
|
|
3782
|
+
const [view, state, language, langJs, autocomplete, commands, highlight] = await Promise.all([
|
|
3783
|
+
import("@codemirror/view"),
|
|
3784
|
+
import("@codemirror/state"),
|
|
3785
|
+
import("@codemirror/language"),
|
|
3786
|
+
import("@codemirror/lang-javascript"),
|
|
3787
|
+
import("@codemirror/autocomplete"),
|
|
3788
|
+
import("@codemirror/commands"),
|
|
3789
|
+
import("@lezer/highlight")
|
|
3790
|
+
]);
|
|
3791
|
+
const { EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection } = view;
|
|
3792
|
+
const { EditorState } = state;
|
|
3793
|
+
const { syntaxHighlighting, HighlightStyle, indentOnInput, bracketMatching } = language;
|
|
3794
|
+
const { javascript } = langJs;
|
|
3795
|
+
const { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } = autocomplete;
|
|
3796
|
+
const { history: history2, defaultKeymap, historyKeymap, indentWithTab } = commands;
|
|
3797
|
+
const { tags } = highlight;
|
|
3798
|
+
const darkHighlight = HighlightStyle.define([
|
|
3799
|
+
{ tag: tags.keyword, color: "#ff7b72" },
|
|
3800
|
+
{ tag: tags.string, color: "#a5d6ff" },
|
|
3801
|
+
{ tag: [tags.function(tags.variableName), tags.function(tags.propertyName)], color: "#d2a8ff" },
|
|
3802
|
+
{ tag: tags.number, color: "#79c0ff" },
|
|
3803
|
+
{ tag: [tags.bool, tags.null], color: "#79c0ff" },
|
|
3804
|
+
{ tag: tags.comment, color: "#8b949e", fontStyle: "italic" },
|
|
3805
|
+
{ tag: [tags.propertyName, tags.attributeName], color: "#7ee787" },
|
|
3806
|
+
{ tag: [tags.typeName, tags.className], color: "#ffa657" },
|
|
3807
|
+
{ tag: tags.operator, color: "#ff7b72" },
|
|
3808
|
+
{ tag: [tags.brace, tags.bracket, tags.punctuation], color: "#c9d1d9" }
|
|
3809
|
+
]);
|
|
3810
|
+
const theme = EditorView.theme(
|
|
3811
|
+
{
|
|
3812
|
+
"&": { color: "#c9d1d9", backgroundColor: "#0d1117", height: "100%", fontSize: "13px" },
|
|
3813
|
+
".cm-scroller": { fontFamily: "'Cascadia Code','Fira Code',Consolas,monospace", overflow: "auto" },
|
|
3814
|
+
".cm-gutters": { backgroundColor: "#0d1117", color: "#484f58", border: "none" },
|
|
3815
|
+
".cm-activeLine": { backgroundColor: "rgba(110,118,129,0.10)" },
|
|
3816
|
+
".cm-activeLineGutter": { backgroundColor: "transparent" },
|
|
3817
|
+
"&.cm-focused": { outline: "none" },
|
|
3818
|
+
".cm-tooltip": { background: "#161b22", border: "1px solid #30363d", color: "#c9d1d9", borderRadius: "6px" },
|
|
3819
|
+
".cm-tooltip-autocomplete ul li[aria-selected]": { background: "#1f6feb", color: "#fff" }
|
|
3820
|
+
},
|
|
3821
|
+
{ dark: true }
|
|
3822
|
+
);
|
|
3823
|
+
const editorView = new EditorView({
|
|
3824
|
+
parent: opts.parent,
|
|
3825
|
+
...opts.root ? { root: opts.root } : {},
|
|
3826
|
+
state: EditorState.create({
|
|
3827
|
+
doc: opts.doc,
|
|
3828
|
+
extensions: [
|
|
3829
|
+
lineNumbers(),
|
|
3830
|
+
highlightActiveLine(),
|
|
3831
|
+
drawSelection(),
|
|
3832
|
+
history2(),
|
|
3833
|
+
indentOnInput(),
|
|
3834
|
+
bracketMatching(),
|
|
3835
|
+
closeBrackets(),
|
|
3836
|
+
javascript({ typescript: true }),
|
|
3837
|
+
syntaxHighlighting(darkHighlight),
|
|
3838
|
+
autocompletion({ override: [cypressCompletionSource] }),
|
|
3839
|
+
keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap, ...completionKeymap, indentWithTab]),
|
|
3840
|
+
theme,
|
|
3841
|
+
EditorView.updateListener.of((update) => {
|
|
3842
|
+
if (update.docChanged && opts.onChange) opts.onChange(update.state.doc.toString());
|
|
3843
|
+
})
|
|
3844
|
+
]
|
|
3845
|
+
})
|
|
3846
|
+
});
|
|
3847
|
+
return {
|
|
3848
|
+
getValue: () => editorView.state.doc.toString(),
|
|
3849
|
+
setValue: (value) => editorView.dispatch({ changes: { from: 0, to: editorView.state.doc.length, insert: value } }),
|
|
3850
|
+
focus: () => editorView.focus(),
|
|
3851
|
+
destroy: () => editorView.destroy()
|
|
3852
|
+
};
|
|
3853
|
+
}
|
|
3854
|
+
|
|
2819
3855
|
// src/index.ts
|
|
2820
3856
|
init_selector_picker();
|
|
2821
3857
|
|
|
@@ -3026,7 +4062,7 @@ var SAVE_TEST_STYLES = `
|
|
|
3026
4062
|
:host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
|
|
3027
4063
|
* { box-sizing: border-box; }
|
|
3028
4064
|
.container { padding: 24px 28px; text-align: left; }
|
|
3029
|
-
.btn-row
|
|
4065
|
+
.btn-row { text-align: center; }
|
|
3030
4066
|
p { margin: 0 0 20px; font-size: 14px; color: #8b949e; line-height: 1.5; }
|
|
3031
4067
|
.btn-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 16px; }
|
|
3032
4068
|
button {
|
|
@@ -3096,10 +4132,11 @@ function renderSaveTestAsk(t) {
|
|
|
3096
4132
|
</div>
|
|
3097
4133
|
</div>`;
|
|
3098
4134
|
}
|
|
3099
|
-
function renderSaveTestDesc(description, notes, tags, t) {
|
|
4135
|
+
function renderSaveTestDesc(description, notes, tags, ticket, t) {
|
|
3100
4136
|
const chipsHtml = tags.map(
|
|
3101
4137
|
(tag) => `<span class="chip">${escHtml(tag)}<button class="chip-del" data-tag="${escAttr(tag)}" title="${t("SAVE_TEST.REMOVE_TAG_TITLE")}">\u2715</button></span>`
|
|
3102
4138
|
).join("");
|
|
4139
|
+
const ticketWarning = ticket.invalid ? `<div class="ticket-warning" style="color:#d29922;font-size:11px;margin-top:2px">${t("SAVE_TEST.TICKET_INVALID")}</div>` : "";
|
|
3103
4140
|
return `
|
|
3104
4141
|
<div class="container">
|
|
3105
4142
|
<p>${t("SAVE_TEST.DESC_LABEL")} (<code>it()</code>):</p>
|
|
@@ -3107,6 +4144,10 @@ function renderSaveTestDesc(description, notes, tags, t) {
|
|
|
3107
4144
|
value="${escAttr(description)}" autocomplete="off" />
|
|
3108
4145
|
<span class="tag-label">${t("SAVE_TEST.NOTES_LABEL")}</span>
|
|
3109
4146
|
<textarea id="notes-input" rows="3" placeholder="${escAttr(t("SAVE_TEST.NOTES_PLACEHOLDER"))}">${escHtml(notes)}</textarea>
|
|
4147
|
+
<span class="tag-label">${t("SAVE_TEST.TICKET_LABEL")}</span>
|
|
4148
|
+
<input id="ticket-input" type="text" placeholder="${escAttr(t(ticket.placeholderKey))}"
|
|
4149
|
+
value="${escAttr(ticket.id)}" autocomplete="off" />
|
|
4150
|
+
${ticketWarning}
|
|
3110
4151
|
<span class="tag-label">${t("SAVE_TEST.TAGS_LABEL")}</span>
|
|
3111
4152
|
<div class="tag-input-row">
|
|
3112
4153
|
<input id="tag-input" type="text" placeholder="${t("SAVE_TEST.TAGS_PLACEHOLDER")}" autocomplete="off" />
|
|
@@ -3128,6 +4169,9 @@ var SaveTestElement = class extends HTMLElement {
|
|
|
3128
4169
|
description = "";
|
|
3129
4170
|
notes = "";
|
|
3130
4171
|
tags = [];
|
|
4172
|
+
ticketId = "";
|
|
4173
|
+
/** Set by the host from config so the ticket placeholder/validation is provider-aware. */
|
|
4174
|
+
issueTrackerProviderId = DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
|
|
3131
4175
|
translation = translationService;
|
|
3132
4176
|
constructor() {
|
|
3133
4177
|
super();
|
|
@@ -3144,19 +4188,20 @@ var SaveTestElement = class extends HTMLElement {
|
|
|
3144
4188
|
this.render();
|
|
3145
4189
|
}
|
|
3146
4190
|
confirmSave() {
|
|
3147
|
-
this.dispatch("savetest", { description: this.description.trim(), notes: this.notes, tags: [...this.tags] });
|
|
4191
|
+
this.dispatch("savetest", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() || null });
|
|
3148
4192
|
}
|
|
3149
4193
|
confirmSaveAndExport() {
|
|
3150
|
-
this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags] });
|
|
4194
|
+
this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() || null });
|
|
3151
4195
|
}
|
|
3152
4196
|
cancel() {
|
|
3153
|
-
this.dispatch("savetest", { description: null, notes: "", tags: [] });
|
|
4197
|
+
this.dispatch("savetest", { description: null, notes: "", tags: [], ticketId: null });
|
|
3154
4198
|
}
|
|
3155
4199
|
restartComponent() {
|
|
3156
4200
|
this._step = "ask";
|
|
3157
4201
|
this.description = "";
|
|
3158
4202
|
this.notes = "";
|
|
3159
4203
|
this.tags = [];
|
|
4204
|
+
this.ticketId = "";
|
|
3160
4205
|
this.render();
|
|
3161
4206
|
}
|
|
3162
4207
|
addTag(tag) {
|
|
@@ -3173,6 +4218,20 @@ var SaveTestElement = class extends HTMLElement {
|
|
|
3173
4218
|
t(key) {
|
|
3174
4219
|
return this.translation.translate(key);
|
|
3175
4220
|
}
|
|
4221
|
+
/** Builds the ticket field view: provider-aware placeholder + soft (non-blocking) format check. */
|
|
4222
|
+
ticketView() {
|
|
4223
|
+
const provider = findIssueTrackerProvider(this.issueTrackerProviderId) ?? ISSUE_TRACKER_PROVIDERS[0];
|
|
4224
|
+
const trimmed = this.ticketId.trim();
|
|
4225
|
+
let invalid = false;
|
|
4226
|
+
if (trimmed) {
|
|
4227
|
+
try {
|
|
4228
|
+
invalid = !new RegExp(provider.idPattern).test(trimmed);
|
|
4229
|
+
} catch {
|
|
4230
|
+
invalid = false;
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
return { id: this.ticketId, placeholderKey: provider.idPlaceholderKey, invalid };
|
|
4234
|
+
}
|
|
3176
4235
|
dispatch(type, detail) {
|
|
3177
4236
|
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
3178
4237
|
}
|
|
@@ -3182,9 +4241,10 @@ var SaveTestElement = class extends HTMLElement {
|
|
|
3182
4241
|
this.shadow.getElementById("btn-yes")?.addEventListener("click", () => this.askSave());
|
|
3183
4242
|
this.shadow.getElementById("btn-no")?.addEventListener("click", () => this.cancel());
|
|
3184
4243
|
} else {
|
|
3185
|
-
this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.notes, this.tags, this.t.bind(this))}`;
|
|
4244
|
+
this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.notes, this.tags, this.ticketView(), this.t.bind(this))}`;
|
|
3186
4245
|
const descInput = this.shadow.getElementById("desc-input");
|
|
3187
4246
|
const notesInput = this.shadow.getElementById("notes-input");
|
|
4247
|
+
const ticketInput = this.shadow.getElementById("ticket-input");
|
|
3188
4248
|
const tagInput = this.shadow.getElementById("tag-input");
|
|
3189
4249
|
descInput.addEventListener("input", () => {
|
|
3190
4250
|
this.description = descInput.value;
|
|
@@ -3192,6 +4252,13 @@ var SaveTestElement = class extends HTMLElement {
|
|
|
3192
4252
|
notesInput.addEventListener("input", () => {
|
|
3193
4253
|
this.notes = notesInput.value;
|
|
3194
4254
|
});
|
|
4255
|
+
ticketInput.addEventListener("input", () => {
|
|
4256
|
+
this.ticketId = ticketInput.value;
|
|
4257
|
+
});
|
|
4258
|
+
ticketInput.addEventListener("change", () => {
|
|
4259
|
+
this.ticketId = ticketInput.value;
|
|
4260
|
+
this.render();
|
|
4261
|
+
});
|
|
3195
4262
|
const tryAddTag = () => {
|
|
3196
4263
|
this.addTag(tagInput.value);
|
|
3197
4264
|
tagInput.value = "";
|
|
@@ -3421,7 +4488,7 @@ function highlightLine(line) {
|
|
|
3421
4488
|
|
|
3422
4489
|
// src/components/test-editor/test-editor.template.ts
|
|
3423
4490
|
function renderTestEditor(state, t) {
|
|
3424
|
-
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest, locale } = state;
|
|
4491
|
+
const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest, groupByTicket, ticketUrlByTest, locale } = state;
|
|
3425
4492
|
const tagFilterHtml = tags.length ? `<div class="tag-filter">
|
|
3426
4493
|
${tags.map((tag) => `<button class="tag-chip${activeTag === tag ? " active" : ""}" data-filter-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}
|
|
3427
4494
|
</div>` : `<div class="tag-filter" style="color:#484f58;font-size:11px">${t("TEST_EDITOR.NO_TAGS")}</div>`;
|
|
@@ -3430,14 +4497,19 @@ function renderTestEditor(state, t) {
|
|
|
3430
4497
|
<input id="describe-name" type="text" placeholder="${t("TEST_EDITOR.DESCRIBE_PLACEHOLDER")}" value="${escAttr(describeName)}" />
|
|
3431
4498
|
<button class="btn-gen-describe" id="btn-gen-describe">${t("TEST_EDITOR.COPY_DESCRIBE")}</button>
|
|
3432
4499
|
</div>` : "";
|
|
3433
|
-
const
|
|
4500
|
+
const renderRow = (test, i) => {
|
|
3434
4501
|
const expanded = expandedIndex === i;
|
|
3435
4502
|
const date = new Date(test.createdAt).toLocaleString(locale, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
3436
4503
|
const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
|
|
3437
4504
|
const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
|
|
4505
|
+
const ticketId = (test.ticketId ?? "").trim();
|
|
4506
|
+
const ticketUrl = ticketUrlByTest[test.id] ?? null;
|
|
4507
|
+
const ticketHtml = !ticketId ? "" : ticketUrl ? `<a class="test-ticket" data-ticket-link href="${escAttr(ticketUrl)}" target="_blank" rel="noopener noreferrer" style="color:#58a6ff;font-size:11px;text-decoration:none">\u{1F3AB} ${escHtml(ticketId)}</a>` : `<span class="test-ticket" style="color:#8b949e;font-size:11px">\u{1F3AB} ${escHtml(ticketId)}</span>`;
|
|
3438
4508
|
const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
|
|
3439
4509
|
const hasIcps = (test.interceptors ?? []).length > 0;
|
|
3440
|
-
const
|
|
4510
|
+
const ticketComment = ticketId ? `${buildTicketComment(ticketId, ticketUrl)}
|
|
4511
|
+
` : "";
|
|
4512
|
+
const itBlockCode = `${ticketComment}it('${escapeSingleQuotes(test.name)}', () => {
|
|
3441
4513
|
${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
3442
4514
|
});`;
|
|
3443
4515
|
const icpBlockCode = icps.length ? `beforeEach(() => {
|
|
@@ -3457,6 +4529,7 @@ ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
3457
4529
|
${checkbox}
|
|
3458
4530
|
<span class="test-name">${escHtml(test.name)}</span>
|
|
3459
4531
|
${tagsHtml}
|
|
4532
|
+
${ticketHtml}
|
|
3460
4533
|
<span class="test-date">${date}</span>
|
|
3461
4534
|
<button class="btn-icon" data-action="copy-cmds" data-idx="${i}" title="${t("TEST_EDITOR.COPY_CMDS_BTN")}">${t("TEST_EDITOR.COPY_CMDS_BTN")}</button>
|
|
3462
4535
|
${hasIcps ? `<button class="btn-icon" data-action="copy-icps" data-idx="${i}" title="${t("TEST_EDITOR.COPY_ICPS_BTN")}">${t("TEST_EDITOR.COPY_ICPS_BTN")}</button>` : ""}
|
|
@@ -3464,17 +4537,40 @@ ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
|
|
|
3464
4537
|
</div>
|
|
3465
4538
|
${body}
|
|
3466
4539
|
</div>`;
|
|
3467
|
-
}
|
|
4540
|
+
};
|
|
4541
|
+
const indexed = visible.map((test, i) => ({ test, i }));
|
|
4542
|
+
const groupedRows = () => {
|
|
4543
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4544
|
+
for (const item of indexed) {
|
|
4545
|
+
const key = (item.test.ticketId ?? "").trim();
|
|
4546
|
+
const bucket = groups.get(key);
|
|
4547
|
+
if (bucket) bucket.push(item);
|
|
4548
|
+
else groups.set(key, [item]);
|
|
4549
|
+
}
|
|
4550
|
+
const keys = [...groups.keys()].sort((a, b) => a === "" ? 1 : b === "" ? -1 : a.localeCompare(b));
|
|
4551
|
+
return keys.map((key) => {
|
|
4552
|
+
const items = groups.get(key) ?? [];
|
|
4553
|
+
const label = key || t("TEST_EDITOR.NO_TICKET_GROUP");
|
|
4554
|
+
const url = key ? ticketUrlByTest[items[0].test.id] ?? null : null;
|
|
4555
|
+
const header = url ? `<a data-ticket-link href="${escAttr(url)}" target="_blank" rel="noopener noreferrer" style="color:#58a6ff;text-decoration:none">\u{1F3AB} ${escHtml(label)}</a>` : `\u{1F3AB} ${escHtml(label)}`;
|
|
4556
|
+
return `<div class="ticket-group">
|
|
4557
|
+
<div class="ticket-group-hd" style="padding:6px 10px;color:#8b949e;font-size:12px;font-weight:600;border-top:1px solid #21262d">${header}</div>
|
|
4558
|
+
${items.map(({ test, i }) => renderRow(test, i)).join("")}
|
|
4559
|
+
</div>`;
|
|
4560
|
+
}).join("");
|
|
4561
|
+
};
|
|
4562
|
+
const listBody = !visible.length ? `<div class="empty">${t("TEST_EDITOR.NO_TESTS")}</div>` : groupByTicket ? groupedRows() : indexed.map(({ test, i }) => renderRow(test, i)).join("");
|
|
3468
4563
|
return `
|
|
3469
4564
|
<div class="toolbar">
|
|
3470
4565
|
${tagFilterHtml}
|
|
4566
|
+
<button class="btn-select${groupByTicket ? " active" : ""}" id="btn-group-ticket">${t("TEST_EDITOR.GROUP_BY_TICKET")}</button>
|
|
3471
4567
|
<button class="btn-select${selectMode ? " active" : ""}" id="btn-select-mode">
|
|
3472
4568
|
${selectMode ? t("TEST_EDITOR.CANCEL_SELECT") : t("TEST_EDITOR.MULTI_SELECT")}
|
|
3473
4569
|
</button>
|
|
3474
4570
|
</div>
|
|
3475
4571
|
${describeBarHtml}
|
|
3476
4572
|
<div class="list">
|
|
3477
|
-
${
|
|
4573
|
+
${listBody}
|
|
3478
4574
|
</div>`;
|
|
3479
4575
|
}
|
|
3480
4576
|
|
|
@@ -3490,6 +4586,9 @@ var TestEditorElement = class extends HTMLElement {
|
|
|
3490
4586
|
selectMode = false;
|
|
3491
4587
|
selectedIds = /* @__PURE__ */ new Set();
|
|
3492
4588
|
describeName = "";
|
|
4589
|
+
groupByTicket = false;
|
|
4590
|
+
issueTrackerProviderId = DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
|
|
4591
|
+
issueTrackerParams = {};
|
|
3493
4592
|
constructor() {
|
|
3494
4593
|
super();
|
|
3495
4594
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -3499,7 +4598,17 @@ var TestEditorElement = class extends HTMLElement {
|
|
|
3499
4598
|
this.loadTests();
|
|
3500
4599
|
}
|
|
3501
4600
|
async loadTests() {
|
|
3502
|
-
|
|
4601
|
+
const [tests, config] = await Promise.all([
|
|
4602
|
+
this.persistence.getAllTests(),
|
|
4603
|
+
this.persistence.getGeneralConfig()
|
|
4604
|
+
]);
|
|
4605
|
+
this.tests = tests;
|
|
4606
|
+
this.issueTrackerProviderId = config?.["issueTrackerProviderId"] ?? DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
|
|
4607
|
+
this.issueTrackerParams = config?.["issueTrackerParams"] ?? {};
|
|
4608
|
+
this.render();
|
|
4609
|
+
}
|
|
4610
|
+
toggleGroupByTicket() {
|
|
4611
|
+
this.groupByTicket = !this.groupByTicket;
|
|
3503
4612
|
this.render();
|
|
3504
4613
|
}
|
|
3505
4614
|
async deleteTest(id) {
|
|
@@ -3574,6 +4683,12 @@ ${beforeEach}${itBlocks}
|
|
|
3574
4683
|
const tags = this.allTags;
|
|
3575
4684
|
const visible = this.visibleTests;
|
|
3576
4685
|
const selectedVisible = visible.filter((t) => this.selectedIds.has(t.id));
|
|
4686
|
+
const ticketUrlByTest = {};
|
|
4687
|
+
for (const test of visible) {
|
|
4688
|
+
if (test.ticketId) {
|
|
4689
|
+
ticketUrlByTest[test.id] = resolveTicketUrl(this.issueTrackerProviderId, this.issueTrackerParams, test.ticketId);
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
3577
4692
|
this.shadow.innerHTML = `<style>${TEST_EDITOR_STYLES}</style>${renderTestEditor({
|
|
3578
4693
|
tags,
|
|
3579
4694
|
visible,
|
|
@@ -3584,9 +4699,15 @@ ${beforeEach}${itBlocks}
|
|
|
3584
4699
|
describeName: this.describeName,
|
|
3585
4700
|
expandedIndex: this.expandedIndex,
|
|
3586
4701
|
interceptorsByTest: this.interceptorsByTest,
|
|
4702
|
+
groupByTicket: this.groupByTicket,
|
|
4703
|
+
ticketUrlByTest,
|
|
3587
4704
|
locale: localeForLang(this.translation.getLang())
|
|
3588
4705
|
}, this.t.bind(this))}`;
|
|
3589
4706
|
this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
|
|
4707
|
+
this.shadow.getElementById("btn-group-ticket")?.addEventListener("click", () => this.toggleGroupByTicket());
|
|
4708
|
+
this.shadow.querySelectorAll("[data-ticket-link]").forEach(
|
|
4709
|
+
(el) => el.addEventListener("click", (ev) => ev.stopPropagation())
|
|
4710
|
+
);
|
|
3590
4711
|
this.shadow.querySelectorAll("[data-filter-tag]").forEach((el) => {
|
|
3591
4712
|
el.addEventListener("click", () => {
|
|
3592
4713
|
const tag = el.dataset["filterTag"] ?? "";
|
|
@@ -3654,12 +4775,22 @@ var CONFIGURATION_STYLES = `
|
|
|
3654
4775
|
* { box-sizing: border-box; }
|
|
3655
4776
|
|
|
3656
4777
|
/* \u2500\u2500 Grid container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
4778
|
+
/* The SweetAlert2 host clips overflow (overflow:hidden), so the grid owns its
|
|
4779
|
+
own scroll \u2014 otherwise the last cards fall below the fold with no scrollbar. */
|
|
3657
4780
|
.cfg-grid {
|
|
3658
4781
|
display: grid;
|
|
3659
4782
|
grid-template-columns: 1fr 1fr;
|
|
3660
4783
|
gap: 10px;
|
|
3661
4784
|
padding: 14px;
|
|
4785
|
+
max-height: 72vh;
|
|
4786
|
+
overflow-y: auto;
|
|
4787
|
+
scrollbar-width: thin;
|
|
4788
|
+
scrollbar-color: #30363d transparent;
|
|
3662
4789
|
}
|
|
4790
|
+
.cfg-grid::-webkit-scrollbar { width: 8px; }
|
|
4791
|
+
.cfg-grid::-webkit-scrollbar-thumb { background: #30363d; border-radius: 4px; }
|
|
4792
|
+
.cfg-grid::-webkit-scrollbar-thumb:hover { background: #484f58; }
|
|
4793
|
+
.cfg-grid::-webkit-scrollbar-track { background: transparent; }
|
|
3663
4794
|
|
|
3664
4795
|
/* \u2500\u2500 Cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
3665
4796
|
.card {
|
|
@@ -3873,8 +5004,33 @@ function renderExportOverlay(state, t) {
|
|
|
3873
5004
|
</div>
|
|
3874
5005
|
</div>`;
|
|
3875
5006
|
}
|
|
5007
|
+
var TRACKER_INPUT_STYLE = "flex:1;min-width:180px;padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;color:#c9d1d9;font-size:12px;outline:none";
|
|
5008
|
+
function renderIssueTrackerCard(state, t) {
|
|
5009
|
+
const { issueTrackerProviderId, issueTrackerParams } = state;
|
|
5010
|
+
const provider = findIssueTrackerProvider(issueTrackerProviderId) ?? ISSUE_TRACKER_PROVIDERS[0];
|
|
5011
|
+
const values = issueTrackerParams[provider.id] ?? {};
|
|
5012
|
+
const options = ISSUE_TRACKER_PROVIDERS.map(
|
|
5013
|
+
(p) => `<option value="${escAttr(p.id)}" ${provider.id === p.id ? "selected" : ""}>${escHtml(t(p.labelKey))}</option>`
|
|
5014
|
+
).join("");
|
|
5015
|
+
const fields = provider.fields.map((f) => `
|
|
5016
|
+
<div class="field-row">
|
|
5017
|
+
<span class="field-label">${escHtml(t(f.labelKey))}</span>
|
|
5018
|
+
<input type="text" data-tracker-field="${escAttr(f.key)}" value="${escAttr(values[f.key] ?? "")}"
|
|
5019
|
+
placeholder="${escAttr(t(f.placeholderKey))}" style="${TRACKER_INPUT_STYLE}" />
|
|
5020
|
+
</div>`).join("");
|
|
5021
|
+
return `
|
|
5022
|
+
<div class="card card-wide">
|
|
5023
|
+
<div class="card-hd">${t("CONFIG.ISSUE_TRACKER_SECTION")}</div>
|
|
5024
|
+
<div class="field-row">
|
|
5025
|
+
<span class="field-label">${t("CONFIG.ISSUE_TRACKER_LABEL")}</span>
|
|
5026
|
+
<select id="issue-tracker-provider">${options}</select>
|
|
5027
|
+
</div>
|
|
5028
|
+
${fields}
|
|
5029
|
+
<div class="check-sub issue-tracker-disclaimer" style="margin-top:8px">${t("CONFIG.ISSUE_TRACKER_DISCLAIMER")}</div>
|
|
5030
|
+
</div>`;
|
|
5031
|
+
}
|
|
3876
5032
|
function renderConfiguration(state, t) {
|
|
3877
|
-
const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes } = state;
|
|
5033
|
+
const { selectedLanguage, advancedHttpConfig, fixtureMode, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes } = state;
|
|
3878
5034
|
const langOptions = LANGS.map(
|
|
3879
5035
|
(l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
|
|
3880
5036
|
).join("");
|
|
@@ -3902,6 +5058,18 @@ function renderConfiguration(state, t) {
|
|
|
3902
5058
|
</label>
|
|
3903
5059
|
</div>
|
|
3904
5060
|
|
|
5061
|
+
<!-- HTTP Fixtures -->
|
|
5062
|
+
<div class="card">
|
|
5063
|
+
<div class="card-hd">${t("CONFIG.FIXTURE_SECTION")}</div>
|
|
5064
|
+
<label class="check-row">
|
|
5065
|
+
<input type="checkbox" id="fixture-toggle" ${fixtureMode ? "checked" : ""} />
|
|
5066
|
+
<div>
|
|
5067
|
+
<div class="check-title">${t("CONFIG.FIXTURE_TITLE")}</div>
|
|
5068
|
+
<div class="check-sub">${t("CONFIG.FIXTURE_SUB")}</div>
|
|
5069
|
+
</div>
|
|
5070
|
+
</label>
|
|
5071
|
+
</div>
|
|
5072
|
+
|
|
3905
5073
|
<!-- Smart Selector -->
|
|
3906
5074
|
<div class="card">
|
|
3907
5075
|
<div class="card-hd">${t("CONFIG.SMART_SELECTOR_SECTION")}</div>
|
|
@@ -3983,6 +5151,9 @@ function renderConfiguration(state, t) {
|
|
|
3983
5151
|
</div>
|
|
3984
5152
|
</div>
|
|
3985
5153
|
|
|
5154
|
+
<!-- Issue tracker (spec 014) -->
|
|
5155
|
+
${renderIssueTrackerCard(state, t)}
|
|
5156
|
+
|
|
3986
5157
|
<!-- Data -->
|
|
3987
5158
|
<div class="card card-wide">
|
|
3988
5159
|
<div class="card-hd">${t("CONFIG.DATA_SECTION")}</div>
|
|
@@ -4006,10 +5177,13 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4006
5177
|
translation;
|
|
4007
5178
|
selectedLanguage = "es";
|
|
4008
5179
|
advancedHttpConfig = false;
|
|
5180
|
+
fixtureMode = false;
|
|
4009
5181
|
selectorStrategy = "data-cy";
|
|
4010
5182
|
smartSelectorEnabled = true;
|
|
4011
5183
|
startHidden = false;
|
|
4012
5184
|
resumeTtlMinutes = DEFAULT_RESUME_TTL_MINUTES;
|
|
5185
|
+
issueTrackerProviderId = DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
|
|
5186
|
+
issueTrackerParams = {};
|
|
4013
5187
|
isExporting = false;
|
|
4014
5188
|
exportMode = "all";
|
|
4015
5189
|
exportTests = [];
|
|
@@ -4021,6 +5195,7 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4021
5195
|
super();
|
|
4022
5196
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
4023
5197
|
this.advancedHttpConfig = localStorage.getItem("extendedHttpCommands") === "true";
|
|
5198
|
+
this.fixtureMode = localStorage.getItem("fixtureMode") === "true";
|
|
4024
5199
|
}
|
|
4025
5200
|
connectedCallback() {
|
|
4026
5201
|
if (!this.persistence) this.persistence = new PersistenceService();
|
|
@@ -4038,6 +5213,7 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4038
5213
|
this.translation.setLang(this.selectedLanguage);
|
|
4039
5214
|
}
|
|
4040
5215
|
this.advancedHttpConfig = localStorage.getItem("extendedHttpCommands") === "true";
|
|
5216
|
+
this.fixtureMode = localStorage.getItem("fixtureMode") === "true";
|
|
4041
5217
|
this.selectorStrategy = config?.["selectorStrategy"] ?? "data-cy";
|
|
4042
5218
|
this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
|
|
4043
5219
|
this.startHidden = config?.["startHidden"] === "true";
|
|
@@ -4049,6 +5225,12 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4049
5225
|
this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
|
|
4050
5226
|
const handle = config?.["cypressDirectoryHandle"];
|
|
4051
5227
|
this.cypressFolderName = handle?.name ?? null;
|
|
5228
|
+
if (config?.["issueTrackerProviderId"] !== void 0) {
|
|
5229
|
+
this.issueTrackerProviderId = config["issueTrackerProviderId"];
|
|
5230
|
+
}
|
|
5231
|
+
if (config?.["issueTrackerParams"] !== void 0) {
|
|
5232
|
+
this.issueTrackerParams = config["issueTrackerParams"];
|
|
5233
|
+
}
|
|
4052
5234
|
this.render();
|
|
4053
5235
|
}
|
|
4054
5236
|
async onLanguageChange(lang) {
|
|
@@ -4063,6 +5245,12 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4063
5245
|
this.persistence.setConfig({ extendedHttpCommands: checked ? "true" : "false" });
|
|
4064
5246
|
this.render();
|
|
4065
5247
|
}
|
|
5248
|
+
onFixtureModeChange(checked) {
|
|
5249
|
+
this.fixtureMode = checked;
|
|
5250
|
+
localStorage.setItem("fixtureMode", checked ? "true" : "false");
|
|
5251
|
+
this.persistence.setConfig({ fixtureMode: checked ? "true" : "false" });
|
|
5252
|
+
this.render();
|
|
5253
|
+
}
|
|
4066
5254
|
async onStartHiddenChange(checked) {
|
|
4067
5255
|
this.startHidden = checked;
|
|
4068
5256
|
await this.persistence.setConfig({ startHidden: checked ? "true" : "false" });
|
|
@@ -4075,6 +5263,20 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4075
5263
|
await this.persistence.setConfig({ [RESUME_TTL_CONFIG_KEY]: safe });
|
|
4076
5264
|
this.render();
|
|
4077
5265
|
}
|
|
5266
|
+
/** Selects the issue-tracker provider (link-only, no sync — spec 014). */
|
|
5267
|
+
async onIssueTrackerProviderChange(id) {
|
|
5268
|
+
this.issueTrackerProviderId = id;
|
|
5269
|
+
await this.persistence.setConfig({ issueTrackerProviderId: id });
|
|
5270
|
+
this.render();
|
|
5271
|
+
}
|
|
5272
|
+
/** Stores a provider field value, scoped to the active provider so switching keeps values. */
|
|
5273
|
+
async onIssueTrackerParamChange(fieldKey, value) {
|
|
5274
|
+
const providerId = this.issueTrackerProviderId;
|
|
5275
|
+
const current = { ...this.issueTrackerParams[providerId] ?? {} };
|
|
5276
|
+
current[fieldKey] = value;
|
|
5277
|
+
this.issueTrackerParams = { ...this.issueTrackerParams, [providerId]: current };
|
|
5278
|
+
await this.persistence.setConfig({ issueTrackerParams: this.issueTrackerParams });
|
|
5279
|
+
}
|
|
4078
5280
|
onResetWidgetPosition() {
|
|
4079
5281
|
this.dispatchEvent(new CustomEvent("resetwidgetposition", { bubbles: true, composed: true }));
|
|
4080
5282
|
showToast(this.t("CONFIG.WIDGET_POSITION_RESET_DONE"));
|
|
@@ -4181,12 +5383,15 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4181
5383
|
this.shadow.innerHTML = `<style>${CONFIGURATION_STYLES}</style>${renderConfiguration({
|
|
4182
5384
|
selectedLanguage: this.selectedLanguage,
|
|
4183
5385
|
advancedHttpConfig: this.advancedHttpConfig,
|
|
5386
|
+
fixtureMode: this.fixtureMode,
|
|
4184
5387
|
selectorStrategy: this.selectorStrategy,
|
|
4185
5388
|
filesystemGranted: this.filesystemGranted,
|
|
4186
5389
|
cypressFolderName: this.cypressFolderName,
|
|
4187
5390
|
smartSelectorEnabled: this.smartSelectorEnabled,
|
|
4188
5391
|
startHidden: this.startHidden,
|
|
4189
5392
|
resumeTtlMinutes: this.resumeTtlMinutes,
|
|
5393
|
+
issueTrackerProviderId: this.issueTrackerProviderId,
|
|
5394
|
+
issueTrackerParams: this.issueTrackerParams,
|
|
4190
5395
|
isExporting: this.isExporting,
|
|
4191
5396
|
exportMode: this.exportMode,
|
|
4192
5397
|
exportTests: this.exportTests,
|
|
@@ -4202,6 +5407,10 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4202
5407
|
"change",
|
|
4203
5408
|
(e) => this.onAdvancedHttpConfigChange(e.target.checked)
|
|
4204
5409
|
);
|
|
5410
|
+
this.shadow.getElementById("fixture-toggle").addEventListener(
|
|
5411
|
+
"change",
|
|
5412
|
+
(e) => this.onFixtureModeChange(e.target.checked)
|
|
5413
|
+
);
|
|
4205
5414
|
this.shadow.getElementById("smart-selector-toggle").addEventListener(
|
|
4206
5415
|
"change",
|
|
4207
5416
|
(e) => this.onSmartSelectorChange(e.target.checked)
|
|
@@ -4219,6 +5428,16 @@ var ConfigurationElement = class extends HTMLElement {
|
|
|
4219
5428
|
"change",
|
|
4220
5429
|
(e) => this.onSelectorStrategyChange(e.target.value)
|
|
4221
5430
|
);
|
|
5431
|
+
this.shadow.getElementById("issue-tracker-provider")?.addEventListener(
|
|
5432
|
+
"change",
|
|
5433
|
+
(e) => this.onIssueTrackerProviderChange(e.target.value)
|
|
5434
|
+
);
|
|
5435
|
+
this.shadow.querySelectorAll("[data-tracker-field]").forEach(
|
|
5436
|
+
(el) => el.addEventListener(
|
|
5437
|
+
"change",
|
|
5438
|
+
() => this.onIssueTrackerParamChange(el.dataset["trackerField"] ?? "", el.value)
|
|
5439
|
+
)
|
|
5440
|
+
);
|
|
4222
5441
|
this.shadow.getElementById("btn-change-folder")?.addEventListener("click", () => this.changeFolder());
|
|
4223
5442
|
this.shadow.getElementById("btn-revoke")?.addEventListener("click", () => this.revokeAccess());
|
|
4224
5443
|
this.shadow.getElementById("btn-export")?.addEventListener("click", () => this.openExportDialog());
|
|
@@ -4821,6 +6040,9 @@ var FILE_PREVIEW_STYLES = `
|
|
|
4821
6040
|
.file-name { flex: 1; font-size: 12px; color: #8b949e;
|
|
4822
6041
|
font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; }
|
|
4823
6042
|
.body { display: flex; flex: 1; overflow: hidden; min-height: 0; }
|
|
6043
|
+
/* Host for the CodeMirror editor (or the textarea fallback). */
|
|
6044
|
+
.editor-host { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
6045
|
+
.editor-host .cm-editor { flex: 1; min-height: 0; }
|
|
4824
6046
|
.editor {
|
|
4825
6047
|
flex: 1; min-width: 0; padding: 14px;
|
|
4826
6048
|
background: #0d1117; color: #c9d1d9; border: none; outline: none;
|
|
@@ -5010,7 +6232,7 @@ function renderFilePreview(state, t) {
|
|
|
5010
6232
|
<button id="btn-copy" title="${t("FILE_PREVIEW.COPY_TITLE")}">\u{1F4CB}</button>
|
|
5011
6233
|
</div>
|
|
5012
6234
|
<div class="body">
|
|
5013
|
-
${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea>`}
|
|
6235
|
+
${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<div class="editor-host" id="editor-host"><textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea></div>`}
|
|
5014
6236
|
${blocksPanelHtml}
|
|
5015
6237
|
</div>
|
|
5016
6238
|
${runResultHtml}
|
|
@@ -5028,6 +6250,7 @@ function renderFilePreview(state, t) {
|
|
|
5028
6250
|
var FilePreviewElement = class extends HTMLElement {
|
|
5029
6251
|
shadow;
|
|
5030
6252
|
textarea = null;
|
|
6253
|
+
editor = null;
|
|
5031
6254
|
fileName = null;
|
|
5032
6255
|
closeLabel = "";
|
|
5033
6256
|
translation = translationService;
|
|
@@ -5053,20 +6276,29 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
5053
6276
|
if (!this.closeLabel) this.closeLabel = this.translation.translate("FILE_PREVIEW.CLOSE_BTN");
|
|
5054
6277
|
this.render();
|
|
5055
6278
|
}
|
|
6279
|
+
disconnectedCallback() {
|
|
6280
|
+
this.editor?.destroy();
|
|
6281
|
+
this.editor = null;
|
|
6282
|
+
}
|
|
5056
6283
|
t(key) {
|
|
5057
6284
|
return this.translation.translate(key);
|
|
5058
6285
|
}
|
|
6286
|
+
/** Current editor content (CodeMirror if upgraded, else the textarea/fallback). */
|
|
6287
|
+
currentValue() {
|
|
6288
|
+
return this.editor?.getValue() ?? this.textarea?.value ?? this._fileContent ?? "";
|
|
6289
|
+
}
|
|
5059
6290
|
get fileContent() {
|
|
5060
6291
|
return this._fileContent;
|
|
5061
6292
|
}
|
|
5062
6293
|
set fileContent(v) {
|
|
5063
6294
|
if (this._originalContent === null) this._originalContent = v;
|
|
5064
6295
|
this._fileContent = v;
|
|
5065
|
-
if (this.
|
|
6296
|
+
if (this.editor) this.editor.setValue(v ?? "");
|
|
6297
|
+
else if (this.textarea) this.textarea.value = v ?? "";
|
|
5066
6298
|
else this.render();
|
|
5067
6299
|
}
|
|
5068
6300
|
saveFile() {
|
|
5069
|
-
const content = this.
|
|
6301
|
+
const content = this.currentValue();
|
|
5070
6302
|
this.dispatchEvent(new CustomEvent("save", { detail: content, bubbles: true, composed: true }));
|
|
5071
6303
|
}
|
|
5072
6304
|
onClose() {
|
|
@@ -5115,7 +6347,7 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
5115
6347
|
* (and warns) when the file has no valid describe() block to insert into.
|
|
5116
6348
|
*/
|
|
5117
6349
|
insertBlocks() {
|
|
5118
|
-
const base = this.
|
|
6350
|
+
const base = this.currentValue();
|
|
5119
6351
|
let content = base;
|
|
5120
6352
|
if (this.interceptorsBlock) {
|
|
5121
6353
|
const merged = advancedTestTransformationService.insertBeforeEach(content, this.interceptorsBlock);
|
|
@@ -5135,12 +6367,14 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
5135
6367
|
showToast(this.t("FILE_PREVIEW.INSERT_DONE"));
|
|
5136
6368
|
}
|
|
5137
6369
|
render() {
|
|
6370
|
+
this.editor?.destroy();
|
|
6371
|
+
this.editor = null;
|
|
5138
6372
|
this.shadow.innerHTML = `<style>${FILE_PREVIEW_STYLES}</style>${renderFilePreview({
|
|
5139
6373
|
fileName: this.fileName,
|
|
5140
6374
|
showDiff: this._showDiff,
|
|
5141
6375
|
fileContent: this._fileContent,
|
|
5142
6376
|
originalContent: this._originalContent,
|
|
5143
|
-
currentContent: this.
|
|
6377
|
+
currentContent: this._fileContent ?? "",
|
|
5144
6378
|
itBlock: this.itBlock,
|
|
5145
6379
|
interceptorsBlock: this.interceptorsBlock,
|
|
5146
6380
|
closeLabel: this.closeLabel,
|
|
@@ -5154,12 +6388,13 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
5154
6388
|
this.textarea.addEventListener("input", () => {
|
|
5155
6389
|
this._fileContent = ta.value;
|
|
5156
6390
|
});
|
|
6391
|
+
void this.upgradeEditor();
|
|
5157
6392
|
}
|
|
5158
6393
|
this.shadow.getElementById("btn-save")?.addEventListener("click", () => this.saveFile());
|
|
5159
6394
|
this.shadow.getElementById("btn-close")?.addEventListener("click", () => this.onClose());
|
|
5160
6395
|
this.shadow.getElementById("btn-launch")?.addEventListener("click", () => this.launchTest());
|
|
5161
6396
|
this.shadow.getElementById("btn-copy")?.addEventListener("click", () => {
|
|
5162
|
-
this.copyToClipboard(this.
|
|
6397
|
+
this.copyToClipboard(this.currentValue());
|
|
5163
6398
|
});
|
|
5164
6399
|
this.shadow.getElementById("btn-diff")?.addEventListener("click", () => this.toggleDiff());
|
|
5165
6400
|
this.shadow.getElementById("btn-insert")?.addEventListener("click", () => this.insertBlocks());
|
|
@@ -5170,11 +6405,188 @@ var FilePreviewElement = class extends HTMLElement {
|
|
|
5170
6405
|
this.copyToClipboard(this.interceptorsBlock);
|
|
5171
6406
|
});
|
|
5172
6407
|
}
|
|
6408
|
+
/**
|
|
6409
|
+
* Lazily upgrades the textarea to a CodeMirror editor (syntax highlight +
|
|
6410
|
+
* Cypress autocomplete). On any failure the textarea is left in place, so the
|
|
6411
|
+
* editor degrades gracefully when CodeMirror can't load.
|
|
6412
|
+
*/
|
|
6413
|
+
async upgradeEditor() {
|
|
6414
|
+
const host = this.shadow.getElementById("editor-host");
|
|
6415
|
+
const ta = this.textarea;
|
|
6416
|
+
if (!host || !ta) return;
|
|
6417
|
+
try {
|
|
6418
|
+
const handle = await createCodeEditor({
|
|
6419
|
+
parent: host,
|
|
6420
|
+
root: this.shadow,
|
|
6421
|
+
doc: this._fileContent ?? "",
|
|
6422
|
+
onChange: (value) => {
|
|
6423
|
+
this._fileContent = value;
|
|
6424
|
+
}
|
|
6425
|
+
});
|
|
6426
|
+
if (!host.isConnected) {
|
|
6427
|
+
handle.destroy();
|
|
6428
|
+
return;
|
|
6429
|
+
}
|
|
6430
|
+
ta.remove();
|
|
6431
|
+
this.textarea = null;
|
|
6432
|
+
this.editor = handle;
|
|
6433
|
+
} catch {
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
5173
6436
|
};
|
|
5174
6437
|
if (!customElements.get("file-preview")) {
|
|
5175
6438
|
customElements.define("file-preview", FilePreviewElement);
|
|
5176
6439
|
}
|
|
5177
6440
|
|
|
6441
|
+
// src/components/help-panel/help-panel.styles.ts
|
|
6442
|
+
var HELP_PANEL_STYLES = `
|
|
6443
|
+
:host { display: block; }
|
|
6444
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
6445
|
+
|
|
6446
|
+
.help-tabs {
|
|
6447
|
+
display: flex;
|
|
6448
|
+
gap: 4px;
|
|
6449
|
+
border-bottom: 1px solid #21262d;
|
|
6450
|
+
margin-bottom: 8px;
|
|
6451
|
+
}
|
|
6452
|
+
.help-tab {
|
|
6453
|
+
appearance: none;
|
|
6454
|
+
border: none;
|
|
6455
|
+
background: transparent;
|
|
6456
|
+
color: #8b949e;
|
|
6457
|
+
font-size: 12px;
|
|
6458
|
+
font-weight: 600;
|
|
6459
|
+
padding: 8px 12px;
|
|
6460
|
+
cursor: pointer;
|
|
6461
|
+
border-bottom: 2px solid transparent;
|
|
6462
|
+
margin-bottom: -1px;
|
|
6463
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
6464
|
+
transition: color .12s, border-color .12s;
|
|
6465
|
+
}
|
|
6466
|
+
.help-tab:hover { color: #c9d1d9; }
|
|
6467
|
+
.help-tab.active { color: #e6edf3; border-bottom-color: #2f81f7; }
|
|
6468
|
+
|
|
6469
|
+
.help-panel {
|
|
6470
|
+
max-height: 60vh;
|
|
6471
|
+
overflow-y: auto;
|
|
6472
|
+
padding: 4px 6px 8px;
|
|
6473
|
+
color: #c9d1d9;
|
|
6474
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
6475
|
+
font-size: 13px;
|
|
6476
|
+
line-height: 1.55;
|
|
6477
|
+
}
|
|
6478
|
+
|
|
6479
|
+
.help-intro {
|
|
6480
|
+
margin: 0 0 12px;
|
|
6481
|
+
color: #8b949e;
|
|
6482
|
+
font-size: 12px;
|
|
6483
|
+
}
|
|
6484
|
+
|
|
6485
|
+
.help-sec { margin: 0 0 14px; }
|
|
6486
|
+
|
|
6487
|
+
.help-sec-hd {
|
|
6488
|
+
margin: 0 0 6px;
|
|
6489
|
+
font-size: 12px;
|
|
6490
|
+
font-weight: 700;
|
|
6491
|
+
color: #e6edf3;
|
|
6492
|
+
letter-spacing: .3px;
|
|
6493
|
+
border-bottom: 1px solid #21262d;
|
|
6494
|
+
padding-bottom: 4px;
|
|
6495
|
+
}
|
|
6496
|
+
|
|
6497
|
+
.help-list {
|
|
6498
|
+
margin: 0;
|
|
6499
|
+
padding: 0;
|
|
6500
|
+
list-style: none;
|
|
6501
|
+
}
|
|
6502
|
+
|
|
6503
|
+
.help-list li {
|
|
6504
|
+
padding: 3px 0 3px 14px;
|
|
6505
|
+
position: relative;
|
|
6506
|
+
color: #c9d1d9;
|
|
6507
|
+
}
|
|
6508
|
+
|
|
6509
|
+
.help-list li::before {
|
|
6510
|
+
content: '\xB7';
|
|
6511
|
+
position: absolute;
|
|
6512
|
+
left: 3px;
|
|
6513
|
+
color: #2f81f7;
|
|
6514
|
+
font-weight: 700;
|
|
6515
|
+
}
|
|
6516
|
+
`;
|
|
6517
|
+
|
|
6518
|
+
// src/components/help-panel/help-panel.template.ts
|
|
6519
|
+
init_html_utils();
|
|
6520
|
+
var REFERENCE_SECTIONS = [
|
|
6521
|
+
{ title: "HELP.SEC_RECORDING", items: ["HELP.RECORDING_1", "HELP.RECORDING_2"] },
|
|
6522
|
+
{
|
|
6523
|
+
title: "HELP.SEC_SHORTCUTS",
|
|
6524
|
+
items: ["HELP.SC_RECORD", "HELP.SC_PAUSE", "HELP.SC_TESTS", "HELP.SC_COMMANDS", "HELP.SC_CONFIG", "HELP.SC_VISIBILITY", "HELP.SC_HELP"]
|
|
6525
|
+
},
|
|
6526
|
+
{
|
|
6527
|
+
title: "HELP.SEC_CAPTURED",
|
|
6528
|
+
items: ["HELP.CAP_CLICK", "HELP.CAP_DBLCLICK", "HELP.CAP_RIGHTCLICK", "HELP.CAP_TYPE", "HELP.CAP_CHECK", "HELP.CAP_SELECT", "HELP.CAP_KEYS", "HELP.CAP_ROUTE", "HELP.CAP_HTTP"]
|
|
6529
|
+
},
|
|
6530
|
+
{ title: "HELP.SEC_ASSERT", items: ["HELP.ASSERT_1", "HELP.ASSERT_2"] },
|
|
6531
|
+
{ title: "HELP.SEC_PANELS", items: ["HELP.PANEL_TESTS", "HELP.PANEL_COMMANDS", "HELP.PANEL_CONFIG", "HELP.PANEL_FILES"] },
|
|
6532
|
+
{ title: "HELP.SEC_SELECTOR", items: ["HELP.SELECTOR_1", "HELP.SELECTOR_2"] },
|
|
6533
|
+
{ title: "HELP.SEC_DATA", items: ["HELP.DATA_1", "HELP.DATA_2"] },
|
|
6534
|
+
{ title: "HELP.SEC_ADVANCED", items: ["HELP.ADV_INVISIBLE", "HELP.ADV_RUNNER", "HELP.ADV_CROSSAPP", "HELP.ADV_DRAG"] }
|
|
6535
|
+
];
|
|
6536
|
+
var GUIDE_SECTIONS = [
|
|
6537
|
+
{ 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"] },
|
|
6538
|
+
{ 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"] },
|
|
6539
|
+
{ 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"] }
|
|
6540
|
+
];
|
|
6541
|
+
function sectionsHtml(sections, t) {
|
|
6542
|
+
return sections.map(
|
|
6543
|
+
(s) => `
|
|
6544
|
+
<section class="help-sec">
|
|
6545
|
+
<h3 class="help-sec-hd">${escHtml(t(s.title))}</h3>
|
|
6546
|
+
<ul class="help-list">${s.items.map((k) => `<li>${escHtml(t(k))}</li>`).join("")}</ul>
|
|
6547
|
+
</section>`
|
|
6548
|
+
).join("");
|
|
6549
|
+
}
|
|
6550
|
+
function renderHelpPanel(t, activeTab = "reference") {
|
|
6551
|
+
const tab = (id, key) => `<button class="help-tab ${activeTab === id ? "active" : ""}" data-tab="${id}">${escHtml(t(key))}</button>`;
|
|
6552
|
+
const body = activeTab === "guide" ? `<p class="help-intro">${escHtml(t("HELP.GUIDE_INTRO"))}</p>${sectionsHtml(GUIDE_SECTIONS, t)}` : `<p class="help-intro">${escHtml(t("HELP.INTRO"))}</p>${sectionsHtml(REFERENCE_SECTIONS, t)}`;
|
|
6553
|
+
return `
|
|
6554
|
+
<div class="help-root">
|
|
6555
|
+
<div class="help-tabs" role="tablist">
|
|
6556
|
+
${tab("reference", "HELP.TAB_REFERENCE")}
|
|
6557
|
+
${tab("guide", "HELP.TAB_GUIDE")}
|
|
6558
|
+
</div>
|
|
6559
|
+
<div class="help-panel" data-tab-panel="${activeTab}">${body}</div>
|
|
6560
|
+
</div>`;
|
|
6561
|
+
}
|
|
6562
|
+
|
|
6563
|
+
// src/components/help-panel/help-panel.ts
|
|
6564
|
+
var HelpPanelElement = class extends HTMLElement {
|
|
6565
|
+
shadow;
|
|
6566
|
+
activeTab = "reference";
|
|
6567
|
+
translation;
|
|
6568
|
+
constructor() {
|
|
6569
|
+
super();
|
|
6570
|
+
this.shadow = this.attachShadow({ mode: "open" });
|
|
6571
|
+
}
|
|
6572
|
+
connectedCallback() {
|
|
6573
|
+
if (!this.translation) this.translation = new TranslationService();
|
|
6574
|
+
this.render();
|
|
6575
|
+
}
|
|
6576
|
+
render() {
|
|
6577
|
+
this.shadow.innerHTML = `<style>${HELP_PANEL_STYLES}</style>${renderHelpPanel(this.translation.translate.bind(this.translation), this.activeTab)}`;
|
|
6578
|
+
this.shadow.querySelectorAll("[data-tab]").forEach((btn) => {
|
|
6579
|
+
btn.addEventListener("click", () => {
|
|
6580
|
+
this.activeTab = btn.dataset["tab"] ?? "reference";
|
|
6581
|
+
this.render();
|
|
6582
|
+
});
|
|
6583
|
+
});
|
|
6584
|
+
}
|
|
6585
|
+
};
|
|
6586
|
+
if (!customElements.get("help-panel")) {
|
|
6587
|
+
customElements.define("help-panel", HelpPanelElement);
|
|
6588
|
+
}
|
|
6589
|
+
|
|
5178
6590
|
// src/components/lib-e2e-recorder/lib-e2e-recorder.ts
|
|
5179
6591
|
var import_sweetalert2 = __toESM(require("sweetalert2"), 1);
|
|
5180
6592
|
|
|
@@ -5182,22 +6594,15 @@ var import_sweetalert2 = __toESM(require("sweetalert2"), 1);
|
|
|
5182
6594
|
var DIRECTIONS = ["up-left", "up-right", "down-left", "down-right"];
|
|
5183
6595
|
function directionBlocks() {
|
|
5184
6596
|
return DIRECTIONS.map((dir) => {
|
|
5185
|
-
const sx = dir.endsWith("left") ? -1 : 1;
|
|
5186
|
-
const sy = dir.startsWith("up") ? -1 : 1;
|
|
5187
6597
|
const tv = dir.startsWith("up") ? "bottom" : "top";
|
|
5188
6598
|
const th = dir.endsWith("left") ? "right" : "left";
|
|
5189
6599
|
const ov = tv === "bottom" ? "top" : "bottom";
|
|
5190
6600
|
const oh = th === "right" ? "left" : "right";
|
|
5191
6601
|
const sel = `.widget[data-expand="${dir}"]`;
|
|
5192
|
-
const labelGeneral = `${sel} .btn-action::after { ${oh}: calc(100% + 9px); ${th}: auto; top: 50%; bottom: auto; transform: translateY(-50%); }`;
|
|
5193
|
-
const labelEnds = `${sel} .btn-action[data-n="1"]::after, ${sel} .btn-action[data-n="4"]::after { ${ov}: calc(100% + 9px); ${tv}: auto; left: 50%; right: auto; transform: translateX(-50%); }`;
|
|
5194
6602
|
return `
|
|
5195
|
-
${sel} {
|
|
5196
|
-
${sel} .btn-
|
|
5197
|
-
${sel} .
|
|
5198
|
-
${sel} .btn-action { ${tv}: 28px; ${th}: 28px; ${ov}: auto; ${oh}: auto; }
|
|
5199
|
-
${labelGeneral}
|
|
5200
|
-
${labelEnds}
|
|
6603
|
+
${sel} .btn-toggle { ${tv}: 24px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
|
|
6604
|
+
${sel} .btn-pause { ${tv}: 78px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
|
|
6605
|
+
${sel} .action-menu { ${tv}: 118px; ${th}: 8px; ${ov}: auto; ${oh}: auto; transform-origin: ${tv} ${th}; }
|
|
5201
6606
|
`;
|
|
5202
6607
|
}).join("\n");
|
|
5203
6608
|
}
|
|
@@ -5207,9 +6612,10 @@ function getRecorderStyles(rec, paused) {
|
|
|
5207
6612
|
*, *::before, *::after { box-sizing: border-box; }
|
|
5208
6613
|
|
|
5209
6614
|
/*
|
|
5210
|
-
*
|
|
5211
|
-
*
|
|
5212
|
-
*
|
|
6615
|
+
* Hit area. Position (left/top) is set from JS so the widget can be dragged;
|
|
6616
|
+
* data-expand orients the action-menu popover toward the viewport interior.
|
|
6617
|
+
* The menu is a DOM child, so hovering it keeps :host(.widget):hover alive even
|
|
6618
|
+
* though it visually overflows this box. Defaults to bottom-right.
|
|
5213
6619
|
*/
|
|
5214
6620
|
.widget {
|
|
5215
6621
|
position: fixed;
|
|
@@ -5218,12 +6624,10 @@ function getRecorderStyles(rec, paused) {
|
|
|
5218
6624
|
width: 190px;
|
|
5219
6625
|
height: 190px;
|
|
5220
6626
|
z-index: 2147483647;
|
|
5221
|
-
--sx: -1;
|
|
5222
|
-
--sy: -1;
|
|
5223
6627
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
5224
6628
|
}
|
|
5225
6629
|
|
|
5226
|
-
/* \u2500\u2500 Toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6630
|
+
/* \u2500\u2500 Toggle (FAB) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5227
6631
|
.btn-toggle {
|
|
5228
6632
|
position: absolute;
|
|
5229
6633
|
bottom: 24px;
|
|
@@ -5276,83 +6680,58 @@ function getRecorderStyles(rec, paused) {
|
|
|
5276
6680
|
.btn-pause:hover { transform: scale(1.12); }
|
|
5277
6681
|
.btn-pause:active { transform: scale(0.93); }
|
|
5278
6682
|
|
|
5279
|
-
/* \u2500\u2500 Action
|
|
5280
|
-
.
|
|
6683
|
+
/* \u2500\u2500 Action menu (labelled grid popover) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
6684
|
+
.action-menu {
|
|
5281
6685
|
position: absolute;
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
align-items: center;
|
|
5292
|
-
justify-content: center;
|
|
5293
|
-
background: rgba(13,17,23,.92);
|
|
6686
|
+
display: grid;
|
|
6687
|
+
grid-template-columns: repeat(3, 1fr);
|
|
6688
|
+
gap: 4px;
|
|
6689
|
+
padding: 8px;
|
|
6690
|
+
width: 174px;
|
|
6691
|
+
background: rgba(13,17,23,.97);
|
|
6692
|
+
border: 1px solid rgba(48,54,61,.9);
|
|
6693
|
+
border-radius: 12px;
|
|
6694
|
+
box-shadow: 0 8px 28px rgba(0,0,0,.5);
|
|
5294
6695
|
backdrop-filter: blur(14px);
|
|
5295
6696
|
-webkit-backdrop-filter: blur(14px);
|
|
5296
|
-
color: #8b949e;
|
|
5297
|
-
box-shadow: 0 4px 18px rgba(0,0,0,.45), 0 0 0 1px rgba(48,54,61,.75);
|
|
5298
6697
|
opacity: 0;
|
|
5299
|
-
transform: scale(0.
|
|
5300
|
-
pointer-events: none;
|
|
5301
|
-
transition: opacity .15s, transform .18s ease-in,
|
|
5302
|
-
background .15s, color .12s, box-shadow .15s;
|
|
5303
|
-
}
|
|
5304
|
-
|
|
5305
|
-
/* Label chip */
|
|
5306
|
-
.btn-action::after {
|
|
5307
|
-
content: attr(data-label);
|
|
5308
|
-
position: absolute;
|
|
5309
|
-
right: calc(100% + 9px);
|
|
5310
|
-
top: 50%;
|
|
5311
|
-
transform: translateY(-50%);
|
|
5312
|
-
background: rgba(13,17,23,.95);
|
|
5313
|
-
color: #e6edf3;
|
|
5314
|
-
font-size: 11px;
|
|
5315
|
-
font-weight: 500;
|
|
5316
|
-
padding: 3px 9px;
|
|
5317
|
-
border-radius: 6px;
|
|
5318
|
-
white-space: nowrap;
|
|
6698
|
+
transform: scale(0.9);
|
|
5319
6699
|
pointer-events: none;
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
border: 1px solid rgba(48,54,61,.8);
|
|
5323
|
-
box-shadow: 0 2px 8px rgba(0,0,0,.35);
|
|
6700
|
+
transition: opacity .16s, transform .2s cubic-bezier(.34,1.56,.64,1);
|
|
6701
|
+
z-index: 3;
|
|
5324
6702
|
}
|
|
5325
|
-
.
|
|
5326
|
-
.btn-action:hover { background: #21262d; color: #e6edf3; }
|
|
5327
|
-
.btn-action:active { background: #30363d !important; }
|
|
5328
|
-
|
|
5329
|
-
/* Expand: spring + stagger */
|
|
5330
|
-
.widget:hover .btn-action {
|
|
6703
|
+
.widget:hover .action-menu {
|
|
5331
6704
|
opacity: 1;
|
|
6705
|
+
transform: scale(1);
|
|
5332
6706
|
pointer-events: all;
|
|
5333
|
-
transition: opacity .2s, transform .32s cubic-bezier(.34,1.56,.64,1),
|
|
5334
|
-
background .15s, color .12s, box-shadow .15s;
|
|
5335
6707
|
}
|
|
5336
6708
|
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
6709
|
+
.action-item {
|
|
6710
|
+
display: flex;
|
|
6711
|
+
flex-direction: column;
|
|
6712
|
+
align-items: center;
|
|
6713
|
+
justify-content: flex-start;
|
|
6714
|
+
gap: 3px;
|
|
6715
|
+
padding: 8px 4px;
|
|
6716
|
+
border: none;
|
|
6717
|
+
border-radius: 8px;
|
|
6718
|
+
cursor: pointer;
|
|
6719
|
+
background: transparent;
|
|
6720
|
+
color: #c9d1d9;
|
|
6721
|
+
font-size: 18px;
|
|
6722
|
+
line-height: 1;
|
|
6723
|
+
transition: background .12s;
|
|
5349
6724
|
}
|
|
5350
|
-
.
|
|
5351
|
-
|
|
5352
|
-
|
|
6725
|
+
.action-item:hover { background: #21262d; }
|
|
6726
|
+
.action-item .label {
|
|
6727
|
+
font-size: 9px;
|
|
6728
|
+
font-weight: 500;
|
|
6729
|
+
color: #8b949e;
|
|
6730
|
+
white-space: nowrap;
|
|
5353
6731
|
}
|
|
6732
|
+
.action-item:hover .label { color: #e6edf3; }
|
|
5354
6733
|
|
|
5355
|
-
/* Per-direction anchors
|
|
6734
|
+
/* Per-direction anchors */
|
|
5356
6735
|
${directionBlocks()}
|
|
5357
6736
|
|
|
5358
6737
|
/* \u2500\u2500 REC / PAUSED badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
@@ -5386,14 +6765,18 @@ function getRecorderStyles(rec, paused) {
|
|
|
5386
6765
|
function renderRecorderWidget(rec, paused, t) {
|
|
5387
6766
|
return `
|
|
5388
6767
|
<div class="widget">
|
|
5389
|
-
<
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
6768
|
+
<div class="action-menu">
|
|
6769
|
+
<button class="action-item" data-action="config">
|
|
6770
|
+
<span class="ico">\u2699\uFE0F</span><span class="label">${t("RECORDER.BTN_CONFIG")}</span></button>
|
|
6771
|
+
<button class="action-item" data-action="browse">
|
|
6772
|
+
<span class="ico">\u{1F4C1}</span><span class="label">${t("RECORDER.BTN_FILES")}</span></button>
|
|
6773
|
+
<button class="action-item" data-action="commands">
|
|
6774
|
+
<span class="ico">\u2328\uFE0F</span><span class="label">${t("RECORDER.BTN_COMMANDS")}</span></button>
|
|
6775
|
+
<button class="action-item" data-action="tests">
|
|
6776
|
+
<span class="ico">\u{1F4CB}</span><span class="label">${t("RECORDER.BTN_TESTS")}</span></button>
|
|
6777
|
+
<button class="action-item" data-action="help">
|
|
6778
|
+
<span class="ico">\u2753</span><span class="label">${t("RECORDER.BTN_HELP")}</span></button>
|
|
6779
|
+
</div>
|
|
5397
6780
|
<button class="btn-pause" data-action="pause"
|
|
5398
6781
|
title="${paused ? t("RECORDER.RESUME_TITLE") : t("RECORDER.PAUSE_TITLE")}">
|
|
5399
6782
|
${paused ? "\u25B6" : "\u23F8"}
|
|
@@ -5445,6 +6828,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
5445
6828
|
isSaveTestDialogOpen = false;
|
|
5446
6829
|
isSettingsDialogOpen = false;
|
|
5447
6830
|
isAdvancedEditorDialogOpen = false;
|
|
6831
|
+
isHelpDialogOpen = false;
|
|
5448
6832
|
constructor() {
|
|
5449
6833
|
super();
|
|
5450
6834
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
@@ -5511,6 +6895,8 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
5511
6895
|
} else {
|
|
5512
6896
|
localStorage.setItem("extendedHttpCommands", config["extendedHttpCommands"] ?? "true");
|
|
5513
6897
|
}
|
|
6898
|
+
const fx = await this.persistence.getConfig("fixtureMode");
|
|
6899
|
+
localStorage.setItem("fixtureMode", fx?.["fixtureMode"] ?? "false");
|
|
5514
6900
|
}
|
|
5515
6901
|
async initLanguage() {
|
|
5516
6902
|
const config = await this.persistence.getConfig("language");
|
|
@@ -5778,6 +7164,9 @@ var LibE2eRecorderElement = class extends HTMLElement {
|
|
|
5778
7164
|
} else if (key === "3") {
|
|
5779
7165
|
event.preventDefault();
|
|
5780
7166
|
this.showSettingsDialog();
|
|
7167
|
+
} else if (key === "h" && event.shiftKey) {
|
|
7168
|
+
event.preventDefault();
|
|
7169
|
+
this.showHelpDialog();
|
|
5781
7170
|
}
|
|
5782
7171
|
}
|
|
5783
7172
|
toggleVisibility() {
|
|
@@ -6019,15 +7408,19 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
6019
7408
|
if (!container) return;
|
|
6020
7409
|
const child = document.createElement("save-test");
|
|
6021
7410
|
child.translation = this.translation;
|
|
7411
|
+
this.persistence.getGeneralConfig().then((config) => {
|
|
7412
|
+
const providerId = config?.["issueTrackerProviderId"];
|
|
7413
|
+
if (providerId) child.issueTrackerProviderId = providerId;
|
|
7414
|
+
});
|
|
6022
7415
|
container.appendChild(child);
|
|
6023
7416
|
child.addEventListener("savetest", (e) => {
|
|
6024
|
-
const { description, notes, tags } = e.detail ?? {};
|
|
6025
|
-
this.onSaveTest(description ?? null, tags ?? [], notes ?? "");
|
|
7417
|
+
const { description, notes, tags, ticketId } = e.detail ?? {};
|
|
7418
|
+
this.onSaveTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? null);
|
|
6026
7419
|
import_sweetalert2.default.close();
|
|
6027
7420
|
});
|
|
6028
7421
|
child.addEventListener("saveandexport", (e) => {
|
|
6029
|
-
const { description, notes, tags } = e.detail ?? {};
|
|
6030
|
-
this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "");
|
|
7422
|
+
const { description, notes, tags, ticketId } = e.detail ?? {};
|
|
7423
|
+
this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? null);
|
|
6031
7424
|
import_sweetalert2.default.close();
|
|
6032
7425
|
});
|
|
6033
7426
|
},
|
|
@@ -6071,6 +7464,31 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
6071
7464
|
this.resizePopup();
|
|
6072
7465
|
});
|
|
6073
7466
|
}
|
|
7467
|
+
showHelpDialog() {
|
|
7468
|
+
this.toggleModal("isHelpDialogOpen", () => {
|
|
7469
|
+
import_sweetalert2.default.fire({
|
|
7470
|
+
title: this.translation.translate("HELP.TITLE"),
|
|
7471
|
+
html: '<div id="help-modal-content" style="padding:0"></div>',
|
|
7472
|
+
showCloseButton: true,
|
|
7473
|
+
showConfirmButton: false,
|
|
7474
|
+
width: 560,
|
|
7475
|
+
color: "#e6edf3",
|
|
7476
|
+
didOpen: () => {
|
|
7477
|
+
makeSwalDraggable();
|
|
7478
|
+
setSwal2DataCyAttribute();
|
|
7479
|
+
const container = document.getElementById("help-modal-content");
|
|
7480
|
+
if (!container) return;
|
|
7481
|
+
const child = document.createElement("help-panel");
|
|
7482
|
+
child.translation = this.translation;
|
|
7483
|
+
container.appendChild(child);
|
|
7484
|
+
},
|
|
7485
|
+
willClose: () => {
|
|
7486
|
+
this.isHelpDialogOpen = false;
|
|
7487
|
+
}
|
|
7488
|
+
});
|
|
7489
|
+
this.resizePopup();
|
|
7490
|
+
});
|
|
7491
|
+
}
|
|
6074
7492
|
showAdvancedEditorDialog(testId) {
|
|
6075
7493
|
this.toggleModal("isAdvancedEditorDialogOpen", () => {
|
|
6076
7494
|
import_sweetalert2.default.fire({
|
|
@@ -6135,7 +7553,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
6135
7553
|
showFileEditorDialog(handle, content, fileName, testId, itBlock = "", interceptorsBlock = "", notes = "") {
|
|
6136
7554
|
import_sweetalert2.default.fire({
|
|
6137
7555
|
title: this.translation.translate("RECORDER.FILE_EDITOR_TITLE"),
|
|
6138
|
-
html: '<div id="file-editor-modal-content" style="
|
|
7556
|
+
html: '<div id="file-editor-modal-content" style="flex:1;min-height:0;display:flex;flex-direction:column;padding:0"></div>',
|
|
6139
7557
|
showCloseButton: false,
|
|
6140
7558
|
showConfirmButton: false,
|
|
6141
7559
|
allowOutsideClick: false,
|
|
@@ -6144,6 +7562,20 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
6144
7562
|
didOpen: () => {
|
|
6145
7563
|
makeSwalDraggable();
|
|
6146
7564
|
setSwal2DataCyAttribute();
|
|
7565
|
+
const popup = import_sweetalert2.default.getPopup();
|
|
7566
|
+
if (popup) {
|
|
7567
|
+
popup.style.height = "640px";
|
|
7568
|
+
const htmlContainer = popup.querySelector(".swal2-html-container");
|
|
7569
|
+
if (htmlContainer) {
|
|
7570
|
+
htmlContainer.style.flex = "1";
|
|
7571
|
+
htmlContainer.style.minHeight = "0";
|
|
7572
|
+
htmlContainer.style.overflow = "hidden";
|
|
7573
|
+
htmlContainer.style.padding = "0";
|
|
7574
|
+
htmlContainer.style.margin = "0";
|
|
7575
|
+
htmlContainer.style.display = "flex";
|
|
7576
|
+
htmlContainer.style.flexDirection = "column";
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
6147
7579
|
const container = document.getElementById("file-editor-modal-content");
|
|
6148
7580
|
if (!container) return;
|
|
6149
7581
|
const child = document.createElement("file-preview");
|
|
@@ -6176,18 +7608,22 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
6176
7608
|
this.resizePopup();
|
|
6177
7609
|
}
|
|
6178
7610
|
// ── save handlers ─────────────────────────────────────────────────────────
|
|
6179
|
-
async onSaveTest(description, tags = [], notes = "") {
|
|
7611
|
+
async onSaveTest(description, tags = [], notes = "", ticketId = null) {
|
|
6180
7612
|
if (description) {
|
|
6181
|
-
|
|
7613
|
+
const fixtures = this.recording.getFixturesSnapshot();
|
|
7614
|
+
await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes, ticketId ?? void 0);
|
|
7615
|
+
await this.writeFixturesIfAny(fixtures);
|
|
6182
7616
|
this.recording.clearCommands();
|
|
6183
7617
|
this.clearRecordingHistory();
|
|
6184
7618
|
this.cypressCommands = [];
|
|
6185
7619
|
this.interceptors = [];
|
|
6186
7620
|
}
|
|
6187
7621
|
}
|
|
6188
|
-
async onSaveAndExportTest(description, tags = [], notes = "") {
|
|
7622
|
+
async onSaveAndExportTest(description, tags = [], notes = "", ticketId = null) {
|
|
6189
7623
|
if (description) {
|
|
6190
|
-
const
|
|
7624
|
+
const fixtures = this.recording.getFixturesSnapshot();
|
|
7625
|
+
const id = await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes, ticketId ?? void 0);
|
|
7626
|
+
await this.writeFixturesIfAny(fixtures);
|
|
6191
7627
|
this.recording.clearCommands();
|
|
6192
7628
|
this.clearRecordingHistory();
|
|
6193
7629
|
this.cypressCommands = [];
|
|
@@ -6195,6 +7631,16 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
6195
7631
|
setTimeout(() => this.showAdvancedEditorDialog(id), 300);
|
|
6196
7632
|
}
|
|
6197
7633
|
}
|
|
7634
|
+
/** Writes captured fixtures to cypress/fixtures and toasts the outcome (spec 012). */
|
|
7635
|
+
async writeFixturesIfAny(fixtures) {
|
|
7636
|
+
if (!fixtures.length) return;
|
|
7637
|
+
try {
|
|
7638
|
+
const n = await this.persistence.writeFixtures(fixtures);
|
|
7639
|
+
showToast(`${this.translation.translate("RECORDER.FIXTURES_WRITTEN_TOAST")} (${n})`);
|
|
7640
|
+
} catch {
|
|
7641
|
+
showToast(this.translation.translate("RECORDER.FIXTURES_NO_FOLDER_TOAST"), false);
|
|
7642
|
+
}
|
|
7643
|
+
}
|
|
6198
7644
|
// ── helpers ───────────────────────────────────────────────────────────────
|
|
6199
7645
|
toggleModal(flag, openFn) {
|
|
6200
7646
|
if (this[flag]) {
|
|
@@ -6230,6 +7676,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
|
|
|
6230
7676
|
this.shadow.querySelector('[data-action="commands"]')?.addEventListener("click", () => this.showCommandsDialog());
|
|
6231
7677
|
this.shadow.querySelector('[data-action="config"]')?.addEventListener("click", () => this.showSettingsDialog());
|
|
6232
7678
|
this.shadow.querySelector('[data-action="browse"]')?.addEventListener("click", () => this.showAdvancedEditorDialog());
|
|
7679
|
+
this.shadow.querySelector('[data-action="help"]')?.addEventListener("click", () => this.showHelpDialog());
|
|
6233
7680
|
this.applyWidgetPosition();
|
|
6234
7681
|
}
|
|
6235
7682
|
};
|
|
@@ -6238,20 +7685,26 @@ if (!customElements.get("lib-e2e-recorder")) {
|
|
|
6238
7685
|
}
|
|
6239
7686
|
|
|
6240
7687
|
// src/index.ts
|
|
6241
|
-
var VERSION = "0.
|
|
7688
|
+
var VERSION = "0.8.0";
|
|
6242
7689
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6243
7690
|
0 && (module.exports = {
|
|
6244
7691
|
ACTIVE_SESSION_BREADCRUMB_KEY,
|
|
6245
7692
|
AdvancedTestEditorElement,
|
|
6246
7693
|
AdvancedTestTransformationService,
|
|
7694
|
+
CHAIN_METHODS,
|
|
7695
|
+
CYPRESS_COMPLETIONS,
|
|
7696
|
+
CY_METHODS,
|
|
6247
7697
|
ConfigurationElement,
|
|
6248
7698
|
DB_SCHEMA,
|
|
6249
7699
|
DB_STORE_NAMES,
|
|
7700
|
+
DEFAULT_ISSUE_TRACKER_PROVIDER_ID,
|
|
6250
7701
|
DEFAULT_RESUME_TTL_MINUTES,
|
|
6251
7702
|
DRAG_THRESHOLD,
|
|
6252
7703
|
FilePreviewElement,
|
|
7704
|
+
HelpPanelElement,
|
|
6253
7705
|
HttpMonitor,
|
|
6254
7706
|
INPUT_TYPES,
|
|
7707
|
+
ISSUE_TRACKER_PROVIDERS,
|
|
6255
7708
|
LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
|
|
6256
7709
|
LOCALE_BY_LANG,
|
|
6257
7710
|
LibE2eRecorderElement,
|
|
@@ -6264,6 +7717,7 @@ var VERSION = "0.6.1";
|
|
|
6264
7717
|
SelectorPickerElement,
|
|
6265
7718
|
Subject,
|
|
6266
7719
|
TOGGLE_MARGIN,
|
|
7720
|
+
TOP_LEVEL,
|
|
6267
7721
|
TestEditorElement,
|
|
6268
7722
|
TestPrevisualizerElement,
|
|
6269
7723
|
TransformationService,
|
|
@@ -6272,9 +7726,16 @@ var VERSION = "0.6.1";
|
|
|
6272
7726
|
advancedTestTransformationService,
|
|
6273
7727
|
boxOffsetForDirection,
|
|
6274
7728
|
boxTopLeftFor,
|
|
7729
|
+
buildTicketComment,
|
|
7730
|
+
buildTicketUrl,
|
|
6275
7731
|
clampTogglePosition,
|
|
7732
|
+
createCodeEditor,
|
|
6276
7733
|
defaultTogglePosition,
|
|
7734
|
+
filterCompletions,
|
|
7735
|
+
filterCypressCompletions,
|
|
7736
|
+
findIssueTrackerProvider,
|
|
6277
7737
|
generateAlias,
|
|
7738
|
+
inferAssertionCommand,
|
|
6278
7739
|
injectStyles,
|
|
6279
7740
|
isLang,
|
|
6280
7741
|
isLocalHost,
|
|
@@ -6284,6 +7745,7 @@ var VERSION = "0.6.1";
|
|
|
6284
7745
|
makeSwalDraggableByContentId,
|
|
6285
7746
|
persistenceService,
|
|
6286
7747
|
resolveExpandDirection,
|
|
7748
|
+
resolveTicketUrl,
|
|
6287
7749
|
selectTestsForExport,
|
|
6288
7750
|
setSwal2DataCyAttribute,
|
|
6289
7751
|
showToast,
|