@@ -23,9 +23,31 @@ const DEVICE_ALIAS_VALIDATION_BTN = '[data-testid="rich-validation-field-deviceA
2323/** Devices scale demo: label applied by devicesimulator (`--label fleet=scale-fleet-00`) */
2424export const SCALE_FLEET_LABEL_TEXT = 'fleet=scale-fleet-00'
2525
26+ /** Real `<input>` inside PatternFly TextInputGroup (`#typeahead-select-input` is the wrapper div). */
27+ const FLEET_LABEL_TYPEAHEAD_INPUT = '#typeahead-select-input input'
28+
2629const enrolledDeviceRows = ( ) =>
2730 cy . get ( '[data-testid="enrolled-devices-table"] tbody tr[data-testid^="enrolled-device-row-"]' )
2831
32+ /**
33+ * Closest ancestor of the enrolled table that also contains this list’s pagination (sibling of the
34+ * table in the DOM). Safer than `#devices-toolbar`.parent() when the console wraps the toolbar.
35+ */
36+ const enrolledDevicesListSection = ( ) =>
37+ cy . get ( '[data-testid="enrolled-devices-table"]' , { timeout : 60000 } ) . parents ( ) . filter ( ( _ , el ) => {
38+ return Cypress . $ ( el ) . find ( 'button[aria-label="Go to next page"]' ) . length > 0
39+ } ) . first ( )
40+
41+ /** PatternFly disables pagination while `isUpdating`; wait for spinner to leave the devices paginator. */
42+ const waitEnrolledPaginationIdle = ( ) => {
43+ enrolledDevicesListSection ( )
44+ . find ( '.pf-v6-c-pagination' )
45+ . first ( )
46+ . should ( ( $p ) => {
47+ expect ( $p . find ( '.pf-v6-c-spinner' ) . length ) . to . eq ( 0 )
48+ } , { timeout : 120000 } )
49+ }
50+
2951/**
3052 * DevicesPage object for device management operations.
3153 * Prefer data-testid selectors from flightctl-ui for stability.
@@ -213,14 +235,18 @@ export const devicesPage = {
213235 filterByFleetScaleLabel : ( ) => {
214236 common . navigateTo ( 'Devices' )
215237 devicesPage . ensureEnrolledDevicesView ( )
216- cy . get ( '#typeahead-select-input' , { timeout : 30000 } ) . should ( 'be.visible' )
217- cy . get ( '#typeahead-select-input' ) . clear ( )
218- cy . get ( '#typeahead-select-input' ) . type ( SCALE_FLEET_LABEL_TEXT )
238+ // Toolbar can sit in overflow:auto regions in ACM/console — avoid visibility flake; interact with force after scroll.
239+ cy . get ( '#devices-toolbar' , { timeout : 30000 } ) . scrollIntoView ( )
240+ cy . get ( FLEET_LABEL_TYPEAHEAD_INPUT , { timeout : 30000 } ) . should ( 'exist' ) . scrollIntoView ( { block : 'center' } )
241+ cy . get ( FLEET_LABEL_TYPEAHEAD_INPUT ) . clear ( { force : true } )
242+ cy . get ( FLEET_LABEL_TYPEAHEAD_INPUT ) . type ( SCALE_FLEET_LABEL_TEXT , { force : true } )
219243 // Label options use `hasCheckbox` in the UI → PatternFly uses role="menuitem", not role="option".
220244 cy . wait ( 1200 )
245+ // Match can resolve to more than one node (e.g. hidden + visible popper, or label + row). Click one.
221246 cy . contains ( '[role="menuitem"], [role="option"]' , SCALE_FLEET_LABEL_TEXT , { timeout : 120000 } )
222- . should ( 'be.visible' )
223- . click ( )
247+ . filter ( ':visible' )
248+ . first ( )
249+ . click ( { force : true } )
224250 // Close the typeahead panel so it does not stay open and block pagination / table clicks.
225251 cy . get ( '[data-testid="list-page-title"]' ) . should ( 'be.visible' ) . click ( )
226252 cy . get ( '[data-testid="enrolled-devices-table"]' , { timeout : 120000 } ) . should ( 'exist' )
@@ -231,37 +257,48 @@ export const devicesPage = {
231257 enrolledDeviceRows ( ) . should ( 'have.length' , expected )
232258 } ,
233259
234- /** “Devices” table is the second paginator when enrollment requests are listed above it . */
260+ /** “Devices” table pagination only (scoped to enrolled list; waits out API refresh disabling controls) . */
235261 clickEnrolledDevicesNextPage : ( ) => {
236- cy . get ( '[data-testid="enrolled-devices-table"]' ) . scrollIntoView ( )
237- cy . get ( 'button[aria-label="Go to next page"]:visible' ) . then ( ( $buttons ) => {
238- const idx = $buttons . length > 1 ? 1 : 0
239- cy . wrap ( $buttons . eq ( idx ) ) . should ( 'not.be.disabled' ) . click ( )
240- } )
241- cy . get ( 'button[aria-label="Go to next page"]:visible' ) . then ( ( $buttons ) => {
242- const idx = $buttons . length > 1 ? 1 : 0
243- cy . wrap ( $buttons . eq ( idx ) ) . should ( 'not.be.disabled' )
262+ cy . get ( '[data-testid="enrolled-devices-table"]' , { timeout : 60000 } ) . should ( 'exist' )
263+ cy . get ( '[data-testid="enrolled-devices-table"]' ) . scrollIntoView ( { block : 'start' } )
264+ enrolledDeviceRows ( ) . should ( 'have.length.at.least' , 1 )
265+ enrolledDeviceRows ( ) . last ( ) . scrollIntoView ( { block : 'end' } )
266+ waitEnrolledPaginationIdle ( )
267+ enrolledDevicesListSection ( ) . within ( ( ) => {
268+ cy . get ( 'button[aria-label="Go to next page"]' , { timeout : 120000 } )
269+ . first ( )
270+ . scrollIntoView ( { block : 'center' , inline : 'center' } )
271+ . should ( 'not.be.disabled' )
272+ . click ( { force : true } )
244273 } )
245274 } ,
246275
276+ /**
277+ * Return to page 1 of the enrolled-devices paginator. Compact PatternFly often omits “Go to first page”,
278+ * so we click “Go to previous page” until it is disabled (same device-table paginator index as next/previous).
279+ */
247280 goToFirstEnrolledDevicesPage : ( ) => {
248- cy . get ( '[data-testid="enrolled-devices-table"]' ) . scrollIntoView ( )
249- cy . get ( 'button[aria-label="Go to first page"]:visible' ) . then ( ( $buttons ) => {
250- if ( $buttons . length === 0 ) {
251- return
252- }
253- const idx = $buttons . length > 1 ? 1 : 0
254- const $btn = $buttons . eq ( idx )
255- if ( ! $btn . is ( ':disabled' ) ) {
256- cy . wrap ( $btn ) . click ( )
257- }
281+ cy . get ( '[data-testid="enrolled-devices-table"]' , { timeout : 60000 } ) . should ( 'exist' )
282+ cy . get ( '[data-testid="enrolled-devices-table"]' ) . scrollIntoView ( { block : 'start' } )
283+ enrolledDeviceRows ( ) . last ( ) . scrollIntoView ( { block : 'end' } )
284+ waitEnrolledPaginationIdle ( )
285+ cy . wrap ( Array . from ( { length : 12 } ) ) . each ( ( ) => {
286+ waitEnrolledPaginationIdle ( )
287+ enrolledDevicesListSection ( ) . within ( ( ) => {
288+ cy . get ( 'button[aria-label="Go to previous page"]' , { timeout : 120000 } )
289+ . first ( )
290+ . then ( ( $prev ) => {
291+ if ( ! $prev . is ( ':disabled' ) ) {
292+ cy . wrap ( $prev ) . scrollIntoView ( { block : 'center' } ) . click ( { force : true } )
293+ }
294+ } )
295+ } )
258296 } )
259- cy . get ( 'button[aria-label="Go to first page"]:visible' ) . then ( ( $buttons ) => {
260- if ( $buttons . length === 0 ) {
261- return
262- }
263- const idx = $buttons . length > 1 ? 1 : 0
264- cy . wrap ( $buttons . eq ( idx ) ) . should ( 'be.disabled' )
297+ waitEnrolledPaginationIdle ( )
298+ enrolledDevicesListSection ( ) . within ( ( ) => {
299+ cy . get ( 'button[aria-label="Go to previous page"]' , { timeout : 120000 } )
300+ . first ( )
301+ . should ( 'be.disabled' )
265302 } )
266303 } ,
267304
0 commit comments