Skip to content

Commit 86d94cb

Browse files
committed
Enhance e2e tests to verify workflow execution completion
Add verifyWorkflowExecutionCompleted() that polls the execution status page until terminal state, catching function failures that were previously invisible.
1 parent 4b27f14 commit 86d94cb

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

e2e/src/pages/WorkflowsPage.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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: /^view$/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(/"message":\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
}

e2e/tests/foundry.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test, expect } from '../src/fixtures';
1+
import { test } from '../src/fixtures';
22

33
test.describe.configure({ mode: 'serial' });
44

@@ -9,16 +9,21 @@ test.describe('Functions with Python - E2E Tests', () => {
99
});
1010

1111
test('should execute Test hello function workflow', async ({ workflowsPage }) => {
12+
test.setTimeout(180000);
1213
await workflowsPage.navigateToWorkflows();
1314
await workflowsPage.executeAndVerifyWorkflow('Test hello function');
15+
await workflowsPage.verifyWorkflowExecutionCompleted();
1416
});
1517

1618
test('should execute Test log-event function workflow', async ({ workflowsPage }) => {
19+
test.setTimeout(180000);
1720
await workflowsPage.navigateToWorkflows();
1821
await workflowsPage.executeAndVerifyWorkflow('Test log-event function');
22+
await workflowsPage.verifyWorkflowExecutionCompleted();
1923
});
2024

2125
test('should execute Test host-details function workflow', async ({ workflowsPage, hostManagementPage }) => {
26+
test.setTimeout(180000);
2227
// Get first available host ID
2328
const hostId = await hostManagementPage.getFirstHostId();
2429

@@ -32,6 +37,7 @@ test.describe('Functions with Python - E2E Tests', () => {
3237
await workflowsPage.executeAndVerifyWorkflow('Test host-details function', {
3338
'Host ID': hostId
3439
});
40+
await workflowsPage.verifyWorkflowExecutionCompleted();
3541
});
3642

3743
test('should render Test servicenow function workflow (without execution)', async ({ workflowsPage }) => {

0 commit comments

Comments
 (0)