@@ -129,7 +129,14 @@ def test_positive_run_default_job_template(
129129
130130 with target_sat .ui_session () as session :
131131 session .organization .select (module_org .name )
132- assert session .host .search (hostname )[0 ]['Name' ] == hostname
132+ # Search with retry to handle potential context/indexing delays
133+ result = wait_for (
134+ lambda : session .host_new .search (hostname ),
135+ timeout = 30 ,
136+ delay = 2 ,
137+ fail_condition = [],
138+ ).out
139+ assert result [0 ]['Name' ] == hostname
133140 command = 'ls'
134141 session .jobinvocation .run (
135142 {
@@ -185,7 +192,17 @@ def test_rex_through_host_details(session, target_sat, rex_contenthost, module_o
185192 )
186193 task_status = target_sat .api .ForemanTask (id = task_result [0 ].id ).poll ()
187194 assert task_status ['result' ] == 'success'
188- recent_jobs = session .host_new .get_details (hostname , "overview.recent_jobs" )['overview' ]
195+
196+ # Wait for recent jobs table to load with job data
197+ recent_jobs = wait_for (
198+ lambda : session .host_new .get_details (hostname , "overview.recent_jobs" )['overview' ],
199+ timeout = 30 ,
200+ delay = 2 ,
201+ fail_condition = lambda result : (
202+ not result .get ('recent_jobs' , {}).get ('finished' , {}).get ('table' )
203+ ),
204+ ).out
205+
189206 assert recent_jobs ['recent_jobs' ]['finished' ]['table' ][0 ]['column0' ] == "Run ls"
190207 assert recent_jobs ['recent_jobs' ]['finished' ]['table' ][0 ]['column2' ] == "succeeded"
191208
@@ -226,7 +243,15 @@ def test_positive_run_custom_job_template(
226243 job_template_name = gen_string ('alpha' )
227244 with target_sat .ui_session () as session :
228245 session .organization .select (module_org .name )
229- assert session .host .search (hostname )[0 ]['Name' ] == hostname
246+ session .location .select (default_location .name )
247+ # Search with retry to handle potential context/indexing delays
248+ result = wait_for (
249+ lambda : session .host_new .search (hostname ),
250+ timeout = 30 ,
251+ delay = 2 ,
252+ fail_condition = [],
253+ ).out
254+ assert result [0 ]['Name' ] == hostname
230255 session .jobtemplate .create (
231256 {
232257 'template.name' : job_template_name ,
@@ -282,9 +307,16 @@ def test_positive_run_job_template_multiple_hosts(
282307 with target_sat .ui_session () as session :
283308 session .organization .select (module_org .name )
284309 for host in host_names :
285- assert session .host .search (host )[0 ]['Name' ] == host
286- session .host .reset_search ()
287- job_status = session .host .schedule_remote_job (
310+ # Search with retry to handle potential context/indexing delays
311+ result = wait_for (
312+ lambda h = host : session .host_new .search (h ),
313+ timeout = 30 ,
314+ delay = 2 ,
315+ fail_condition = [],
316+ ).out
317+ assert result [0 ]['Name' ] == host
318+ session .host_new .reset_search ()
319+ job_status = session .host_new .schedule_remote_job (
288320 host_names ,
289321 {
290322 'category_and_template.job_category' : 'Commands' ,
@@ -326,10 +358,17 @@ def test_positive_run_scheduled_job_template_by_ip(session, module_org, rex_cont
326358 with session :
327359 session .organization .select (module_org .name )
328360 session .location .select ('Default Location' )
329- assert session .host .search (hostname )[0 ]['Name' ] == hostname
361+ # Search with retry to handle potential context/indexing delays
362+ result = wait_for (
363+ lambda : session .host_new .search (hostname ),
364+ timeout = 30 ,
365+ delay = 2 ,
366+ fail_condition = [],
367+ ).out
368+ assert result [0 ]['Name' ] == hostname
330369 plan_time = session .browser .get_client_datetime () + datetime .timedelta (seconds = job_time )
331370 command_to_run = 'sleep 10'
332- job_status = session .host .schedule_remote_job (
371+ job_status = session .host_new .schedule_remote_job (
333372 [hostname ],
334373 {
335374 'category_and_template.job_category' : 'Commands' ,
@@ -341,52 +380,55 @@ def test_positive_run_scheduled_job_template_by_ip(session, module_org, rex_cont
341380 },
342381 wait_for_results = False ,
343382 )
344- # Note that to create this host scheduled job we spent some time from that plan_time, as it
345- # was calculated before creating the job
383+ # Track state transitions through polling
346384 job_left_time = (plan_time - session .browser .get_client_datetime ()).total_seconds ()
347- # assert that we have time left to wait, otherwise we have to use more job time,
348- # the job_time must be significantly greater than job creation time.
349- assert job_left_time > 0
385+ assert job_left_time > 0 , "Job scheduled time already passed during creation"
386+
387+ # Initial state: should be waiting to start
350388 assert job_status ['hosts' ][0 ]['Name' ] == hostname
351389 assert job_status ['hosts' ][0 ]['Status' ] in ('Awaiting start' , 'N/A' )
352- # sleep 3/4 of the left time
353- time .sleep (job_left_time * 3 / 4 )
354- job_status = session .jobinvocation .read (f'Run { command_to_run } ' , hostname , 'hosts' )
355- assert job_status ['hosts' ][0 ]['Name' ] == hostname
356- assert job_status ['hosts' ][0 ]['Status' ] in (
357- 'Awaiting start' ,
358- 'N/A' ,
359- 'Succeeded' ,
360- )
361- # recalculate the job left time to be more accurate
362- job_left_time = (plan_time - session .browser .get_client_datetime ()).total_seconds ()
363- # the last read time should not take more than 1/4 of the last left time
364- assert job_left_time > 0
365- wait_for (
366- lambda : (
367- session .jobinvocation .read (f'Run { command_to_run } ' , hostname , 'hosts' )['hosts' ][0 ][
368- 'Status'
369- ]
370- == 'Pending'
371- ),
372- timeout = (job_left_time + 30 ),
373- delay = 1 ,
374- )
375- # wait the job to change status to "Succeeded"
376- wait_for (
377- lambda : (
378- session .jobinvocation .read (f'Run { command_to_run } ' , hostname , 'hosts' )['hosts' ][0 ][
379- 'Status'
380- ]
381- == 'Succeeded'
382- ),
383- timeout = 30 ,
384- delay = 1 ,
385- )
390+
391+ # Poll for state transitions and record them
392+ observed_states = [job_status ['hosts' ][0 ]['Status' ]]
393+ poll_timeout = job_left_time + 60 # Extra buffer for job execution
394+ poll_start = time .time ()
395+
396+ while time .time () - poll_start < poll_timeout :
397+ job_status = session .jobinvocation .read (f'Run { command_to_run } ' , hostname , 'hosts' )
398+ current_status = job_status ['hosts' ][0 ]['Status' ]
399+
400+ # Record state changes
401+ if current_status != observed_states [- 1 ]:
402+ observed_states .append (current_status )
403+
404+ # Exit when job completes
405+ if current_status in ('Succeeded' , 'Failed' , 'Cancelled' ):
406+ break
407+
408+ time .sleep (2 )
409+
410+ # Read final complete status
386411 job_status = session .jobinvocation .read (f'Run { command_to_run } ' , hostname )
412+ final_status = job_status ['hosts' ][0 ]['Status' ]
413+ if final_status != observed_states [- 1 ]:
414+ observed_states .append (final_status )
415+
416+ # Verify state progression
417+ # Expected: Awaiting start/N/A -> Running/Pending -> Succeeded
418+ assert 'Succeeded' in observed_states , f"Job never succeeded. States: { observed_states } "
419+ assert observed_states [- 1 ] == 'Succeeded' , f"Final state not Succeeded: { observed_states } "
420+
421+ # Verify it went through a running state (not immediate execution)
422+ has_pre_run_state = any (s in ('Awaiting start' , 'N/A' ) for s in observed_states [:- 1 ])
423+ has_running_state = any (s in ('Pending' , 'Running' ) for s in observed_states )
424+ assert has_pre_run_state , f"Missing pre-run state. States: { observed_states } "
425+ assert has_running_state or len (observed_states ) == 2 , (
426+ f"Job should go through running state or transition directly. States: { observed_states } "
427+ )
428+
429+ # Final assertions
387430 assert job_status ['overall_status' ]['is_success' ]
388431 assert job_status ['hosts' ][0 ]['Name' ] == hostname
389- assert job_status ['hosts' ][0 ]['Status' ] == 'Succeeded'
390432
391433
392434@pytest .mark .rhel_ver_list ([settings .content_host .default_rhel_version ])
0 commit comments