From 94fba6cadb4b23f16fc868c1e8307fc710d102ab Mon Sep 17 00:00:00 2001 From: Tal Hilzenrat Date: Tue, 3 Mar 2026 15:14:41 -0500 Subject: [PATCH] Add data-testid attributes for Cypress device E2E tests - AppLayout: nav-toggle on sidebar toggle - ListPage: list-page-title - Enrollment: enrollment-request-approve-button, approve-device-form-submit - EnrolledDevicesTable: enrolled-devices-table, toolbar-decommission-devices, show-decommissioned-devices-switch - EnrolledDeviceTableRow: enrolled-device-row, device-name-link, device-row-actions - ResourceLink: optional data-testid prop (including fallback when routeLink is missing) - FlightCtlWizardFooter: wizard-next-button, wizard-save-button, wizard-back-button, wizard-close-button, wizard-cancel-button - DetailsPage: optional titleDataTestId (device-details-title from DeviceDetailsPage) - MassDecommissionDeviceModal: modal-decommission-confirm - MassDeleteDeviceModal: modal-delete-devices-confirm - DecommissionedDevicesTable: toolbar-delete-forever, show-decommissioned-devices-switch - Table: data-testid prop support Addresses CodeRabbit nitpicks for ResourceLink fallback and wizard buttons. Made-with: Cursor --- .../app/components/AppLayout/AppLayout.tsx | 1 + .../components/DetailsPage/DetailsPage.tsx | 10 ++++++- .../DeviceDetails/DeviceDetailsPage.tsx | 1 + .../DecommissionedDevicesTable.tsx | 2 ++ .../DevicesPage/EnrolledDeviceTableRow.tsx | 11 +++++--- .../DevicesPage/EnrolledDevicesTable.tsx | 3 +++ .../EnrollmentRequestTableRow.tsx | 2 +- .../src/components/ListPage/ListPage.tsx | 2 +- .../src/components/Table/Table.tsx | 1 + .../common/FlightCtlWizardFooter.tsx | 26 ++++++++++++++++--- .../src/components/common/ResourceLink.tsx | 17 ++++++++++-- .../ApproveDeviceModal/ApproveDeviceForm.tsx | 1 + .../MassDecommissionDeviceModal.tsx | 1 + .../MassDeleteDeviceModal.tsx | 9 ++++++- 14 files changed, 74 insertions(+), 13 deletions(-) diff --git a/apps/standalone/src/app/components/AppLayout/AppLayout.tsx b/apps/standalone/src/app/components/AppLayout/AppLayout.tsx index a7997c645..397c629f2 100644 --- a/apps/standalone/src/app/components/AppLayout/AppLayout.tsx +++ b/apps/standalone/src/app/components/AppLayout/AppLayout.tsx @@ -50,6 +50,7 @@ const AppLayoutContent = () => { isSidebarOpen={isSidebarOpen} onSidebarToggle={onSidebarToggle} id="page-toggle-button" + data-testid="nav-toggle" /> diff --git a/libs/ui-components/src/components/DetailsPage/DetailsPage.tsx b/libs/ui-components/src/components/DetailsPage/DetailsPage.tsx index 256f08da1..bca84dc56 100644 --- a/libs/ui-components/src/components/DetailsPage/DetailsPage.tsx +++ b/libs/ui-components/src/components/DetailsPage/DetailsPage.tsx @@ -38,6 +38,8 @@ export type DetailsPageProps = { actions?: React.ReactNode; nav?: React.ReactNode; banner?: React.ReactNode; + /** Optional data-testid for the title (e.g. "device-details-title") */ + titleDataTestId?: string; }; const DetailsPage = ({ @@ -53,6 +55,7 @@ const DetailsPage = ({ actions, nav, banner, + titleDataTestId, }: DetailsPageProps) => { const { t } = useTranslation(); let content = children; @@ -87,7 +90,12 @@ const DetailsPage = ({ - + <Title + headingLevel="h1" + size="3xl" + role="heading" + {...(titleDataTestId && { 'data-testid': titleDataTestId })} + > {title || id} diff --git a/libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx b/libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx index 3df653123..20aad90d4 100644 --- a/libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx +++ b/libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx @@ -121,6 +121,7 @@ const DeviceDetailsPage = ({ children, hideTerminal }: DeviceDetailsPageProps) = error={error} id={deviceId} breadcrumbTitle={deviceAlias} + titleDataTestId="device-details-title" title={ canEdit ? ( /* key={deviceAlias} is needed for the input field to be initialized with the alias as its value */ diff --git a/libs/ui-components/src/components/Device/DevicesPage/DecommissionedDevicesTable.tsx b/libs/ui-components/src/components/Device/DevicesPage/DecommissionedDevicesTable.tsx index 54d6fa4b9..c71f32d7b 100644 --- a/libs/ui-components/src/components/Device/DevicesPage/DecommissionedDevicesTable.tsx +++ b/libs/ui-components/src/components/Device/DevicesPage/DecommissionedDevicesTable.tsx @@ -94,6 +94,7 @@ const DecommissionedDevicesTable = ({ isDisabled={!hasSelectedRows} onClick={() => setIsMassDeleteModalOpen(true)} variant="secondary" + data-testid="toolbar-delete-forever" > {t('Delete forever')} @@ -108,6 +109,7 @@ const DecommissionedDevicesTable = ({ setOnlyDecommissioned(false); }} ouiaId={t('Show decommissioned devices')} + data-testid="show-decommissioned-devices-switch" /> diff --git a/libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx b/libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx index 14e1180e3..acb1032c7 100644 --- a/libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx +++ b/libs/ui-components/src/components/Device/DevicesPage/EnrolledDeviceTableRow.tsx @@ -54,7 +54,7 @@ const EnrolledDeviceTableRow = ({ const columnIds = React.useMemo(() => deviceColumns.map(({ id }) => id), [deviceColumns]); return ( - + {columnIds.includes('alias') && ( - + )} {columnIds.includes('name') && ( @@ -94,7 +99,7 @@ const EnrolledDeviceTableRow = ({ )} {!hideActions && ( - + setIsMassDecommissionModalOpen(true)} variant="secondary" + data-testid="toolbar-decommission-devices" > {t('Decommission devices')} @@ -177,6 +178,7 @@ const EnrolledDevicesTable = ({ setOnlyDecommissioned(true); }} ouiaId={t('Show decommissioned devices')} + data-testid="show-decommissioned-devices-switch" /> @@ -189,6 +191,7 @@ const EnrolledDevicesTable = ({ emptyData={devices.length === 0} isAllSelected={isAllSelected} onSelectAll={setAllSelected} + data-testid="enrolled-devices-table" > {devices.map((device, index) => ( diff --git a/libs/ui-components/src/components/EnrollmentRequest/EnrollmentRequestTableRow.tsx b/libs/ui-components/src/components/EnrollmentRequest/EnrollmentRequestTableRow.tsx index b66b7fdff..0fa0b2edf 100644 --- a/libs/ui-components/src/components/EnrollmentRequest/EnrollmentRequestTableRow.tsx +++ b/libs/ui-components/src/components/EnrollmentRequest/EnrollmentRequestTableRow.tsx @@ -67,7 +67,7 @@ const EnrollmentRequestTableRow: React.FC = ({ {timeSinceText(t, er.metadata.creationTimestamp)} {canApprove && ( - diff --git a/libs/ui-components/src/components/ListPage/ListPage.tsx b/libs/ui-components/src/components/ListPage/ListPage.tsx index d23e7c82d..825dd2c7c 100644 --- a/libs/ui-components/src/components/ListPage/ListPage.tsx +++ b/libs/ui-components/src/components/ListPage/ListPage.tsx @@ -13,7 +13,7 @@ const ListPage: React.FC = ({ title, headingLevel = 'h1', childre - + <Title headingLevel={headingLevel} size="3xl" data-testid="list-page-title"> {title} diff --git a/libs/ui-components/src/components/Table/Table.tsx b/libs/ui-components/src/components/Table/Table.tsx index 36fc43702..8b82ff566 100644 --- a/libs/ui-components/src/components/Table/Table.tsx +++ b/libs/ui-components/src/components/Table/Table.tsx @@ -38,6 +38,7 @@ type TableProps = Pick & { onSelectAll?: (isSelected: boolean) => void; isAllSelected?: boolean; isExpandable?: boolean; + 'data-testid'?: string; singleSelect?: boolean; }; diff --git a/libs/ui-components/src/components/common/FlightCtlWizardFooter.tsx b/libs/ui-components/src/components/common/FlightCtlWizardFooter.tsx index 4e4f739a3..d678ce79c 100644 --- a/libs/ui-components/src/components/common/FlightCtlWizardFooter.tsx +++ b/libs/ui-components/src/components/common/FlightCtlWizardFooter.tsx @@ -50,20 +50,32 @@ const FlightCtlWizardFooter = >({ let primaryBtn: React.ReactNode; if (isSubmitStep && !isReadOnly) { primaryBtn = ( - ); } else if (isSubmitStep) { // Read-only "Review" step primaryBtn = ( - ); } else { primaryBtn = ( - ); @@ -77,6 +89,7 @@ const FlightCtlWizardFooter = >({ variant="secondary" onClick={goToPrevStep} isDisabled={String(activeStep.id) === firstStepId || isSubmitting} + data-testid="wizard-back-button" > {t('Back')} @@ -85,7 +98,12 @@ const FlightCtlWizardFooter = >({ - diff --git a/libs/ui-components/src/components/common/ResourceLink.tsx b/libs/ui-components/src/components/common/ResourceLink.tsx index 35c13120a..f0075f035 100644 --- a/libs/ui-components/src/components/common/ResourceLink.tsx +++ b/libs/ui-components/src/components/common/ResourceLink.tsx @@ -12,6 +12,7 @@ type ResourceDisplayLinkProps = { name?: string; variant?: 'shortened' | 'full'; routeLink?: RouteWithPostfix; + 'data-testid'?: string; }; export const getDisplayText = (name: string | undefined) => { @@ -24,7 +25,13 @@ export const getDisplayText = (name: string | undefined) => { return `${name.substring(0, 6)}...${name.substring(name.length - 7)}`; }; -const ResourceLink = ({ id, name, variant = 'shortened', routeLink }: ResourceDisplayLinkProps) => { +const ResourceLink = ({ + id, + name, + variant = 'shortened', + routeLink, + 'data-testid': dataTestId, +}: ResourceDisplayLinkProps) => { const nameOrId = name || id; const displayText = getDisplayText(nameOrId); const showCopy = nameOrId !== displayText; @@ -33,7 +40,13 @@ const ResourceLink = ({ id, name, variant = 'shortened', routeLink }: ResourceDi return ( - {routeLink ? {textEl} : <>{textEl}} + {routeLink ? ( + + {textEl} + + ) : ( + {textEl} + )} {showCopy && nameOrId && } ); diff --git a/libs/ui-components/src/components/modals/ApproveDeviceModal/ApproveDeviceForm.tsx b/libs/ui-components/src/components/modals/ApproveDeviceModal/ApproveDeviceForm.tsx index ac8ef9680..28d8b4f8e 100644 --- a/libs/ui-components/src/components/modals/ApproveDeviceModal/ApproveDeviceForm.tsx +++ b/libs/ui-components/src/components/modals/ApproveDeviceModal/ApproveDeviceForm.tsx @@ -59,6 +59,7 @@ const ApproveDeviceForm: React.FC = ({ enrollmentRequest onClick={submitForm} isDisabled={disableSubmit || isSubmitting} isLoading={isSubmitting} + data-testid="approve-device-form-submit" > {t('Approve')} diff --git a/libs/ui-components/src/components/modals/massModals/MassDecommissionDeviceModal/MassDecommissionDeviceModal.tsx b/libs/ui-components/src/components/modals/massModals/MassDecommissionDeviceModal/MassDecommissionDeviceModal.tsx index 090e551fb..eb5834a2c 100644 --- a/libs/ui-components/src/components/modals/massModals/MassDecommissionDeviceModal/MassDecommissionDeviceModal.tsx +++ b/libs/ui-components/src/components/modals/massModals/MassDecommissionDeviceModal/MassDecommissionDeviceModal.tsx @@ -124,6 +124,7 @@ const MassDecommissionDeviceModal = ({ onClose, devices, onSuccess }: MassDecomm onClick={decommissionDevices} isLoading={isSubmitting} isDisabled={isSubmitting} + data-testid="modal-decommission-confirm" > {t('Decommission')} diff --git a/libs/ui-components/src/components/modals/massModals/MassDeleteDeviceModal/MassDeleteDeviceModal.tsx b/libs/ui-components/src/components/modals/massModals/MassDeleteDeviceModal/MassDeleteDeviceModal.tsx index 783db9207..fe632d3d1 100644 --- a/libs/ui-components/src/components/modals/massModals/MassDeleteDeviceModal/MassDeleteDeviceModal.tsx +++ b/libs/ui-components/src/components/modals/massModals/MassDeleteDeviceModal/MassDeleteDeviceModal.tsx @@ -112,7 +112,14 @@ const MassDeleteDeviceModal: React.FC = ({ onClose, -