@@ -246,4 +246,81 @@ export class WorkflowsPage extends BasePage {
246246 `Execute and verify workflow: ${ workflowName } `
247247 ) ;
248248 }
249+
250+ /**
251+ * Check the actual execution status by viewing the execution details.
252+ * Navigates to the execution log, waits for the execution to complete,
253+ * expands the execution row, and checks the status.
254+ */
255+ async verifyWorkflowExecutionCompleted ( timeoutMs = 120000 ) : Promise < void > {
256+ return this . withTiming (
257+ async ( ) => {
258+ this . logger . info ( 'Checking workflow execution status in detail view' ) ;
259+
260+ // The "View" link opens in a new tab - capture it
261+ const viewLink = this . page . getByRole ( 'link' , { name : / ^ v i e w $ / i } ) ;
262+ await viewLink . waitFor ( { state : 'visible' , timeout : 10000 } ) ;
263+
264+ const [ executionPage ] = await Promise . all ( [
265+ this . page . context ( ) . waitForEvent ( 'page' ) ,
266+ viewLink . click ( ) ,
267+ ] ) ;
268+
269+ // Wait for the new tab to load (execution pages can be slow to render)
270+ await executionPage . waitForLoadState ( 'networkidle' ) ;
271+ await executionPage . waitForLoadState ( 'domcontentloaded' ) ;
272+ this . logger . info ( 'Execution page opened in new tab' ) ;
273+
274+ // Wait for "Execution status" to appear (proves execution details loaded)
275+ const statusLabel = executionPage . getByText ( 'Execution status' ) ;
276+ await statusLabel . waitFor ( { state : 'visible' , timeout : 60000 } ) ;
277+ this . logger . info ( 'Execution details visible' ) ;
278+
279+ // Poll until execution reaches a terminal state
280+ this . logger . info ( `Waiting up to ${ timeoutMs / 1000 } s for execution to complete...` ) ;
281+
282+ const startTime = Date . now ( ) ;
283+ while ( Date . now ( ) - startTime < timeoutMs ) {
284+ // Re-find status label each iteration (DOM recreated on reload)
285+ const currentStatusLabel = executionPage . getByText ( 'Execution status' ) ;
286+ await currentStatusLabel . waitFor ( { state : 'visible' , timeout : 15000 } ) ;
287+ const statusContainer = currentStatusLabel . locator ( '..' ) ;
288+ const statusText = await statusContainer . textContent ( ) || '' ;
289+ const currentStatus = statusText . replace ( 'Execution status' , '' ) . trim ( ) ;
290+ this . logger . info ( `Current status: ${ currentStatus } ` ) ;
291+
292+ if ( currentStatus . toLowerCase ( ) . includes ( 'failed' ) ) {
293+ // Capture error details
294+ const pageContent = await executionPage . textContent ( 'body' ) || '' ;
295+ const messageMatch = pageContent . match ( / " m e s s a g e " : \s * " ( [ ^ " ] + ) " / ) ;
296+
297+ let errorMessage = 'Workflow action failed' ;
298+ if ( messageMatch ) {
299+ errorMessage = messageMatch [ 1 ] ;
300+ }
301+
302+ await executionPage . close ( ) ;
303+ this . logger . error ( `Workflow execution failed: ${ errorMessage } ` ) ;
304+ throw new Error ( `Workflow execution failed: ${ errorMessage } ` ) ;
305+ }
306+
307+ if ( ! currentStatus . toLowerCase ( ) . includes ( 'in progress' ) ) {
308+ // Terminal state that isn't "Failed"
309+ await executionPage . close ( ) ;
310+ this . logger . success ( `Workflow execution completed with status: ${ currentStatus } ` ) ;
311+ return ;
312+ }
313+
314+ await executionPage . waitForTimeout ( 5000 ) ;
315+
316+ // Reload to get updated status - the page doesn't auto-refresh
317+ await executionPage . reload ( { waitUntil : 'networkidle' } ) ;
318+ }
319+
320+ await executionPage . close ( ) ;
321+ throw new Error ( 'Workflow execution timed out - still in progress' ) ;
322+ } ,
323+ 'Verify workflow execution completed'
324+ ) ;
325+ }
249326}
0 commit comments