Skip to content

Commit fdc1aa2

Browse files
committed
Revert "Refactor e2e tests for errors"
This reverts commit 1bab22c.
1 parent 1bab22c commit fdc1aa2

File tree

3 files changed

+260
-372
lines changed

3 files changed

+260
-372
lines changed

packages/core/e2e/e2e.test.ts

Lines changed: 112 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -585,202 +585,66 @@ describe('e2e', () => {
585585
expect(returnValue).toEqual([0, 1, 2, 3, 4]);
586586
});
587587

588-
// ==================== ERROR HANDLING TESTS ====================
589-
describe('error handling', () => {
590-
describe('error propagation', () => {
591-
describe('workflow errors', () => {
592-
test(
593-
'nested function calls preserve message and stack trace',
594-
{ timeout: 60_000 },
595-
async () => {
596-
const run = await triggerWorkflow('errorWorkflowNested', []);
597-
const result = await getWorkflowReturnValue(run.runId);
598-
599-
expect(result.name).toBe('WorkflowRunFailedError');
600-
expect(result.cause.message).toContain('Nested workflow error');
601-
// Stack shows call chain: errorNested1 -> errorNested2 -> errorNested3
602-
expect(result.cause.stack).toContain('errorNested1');
603-
expect(result.cause.stack).toContain('errorNested2');
604-
expect(result.cause.stack).toContain('errorNested3');
605-
expect(result.cause.stack).toContain('errorWorkflowNested');
606-
expect(result.cause.stack).toContain('99_e2e.ts');
607-
expect(result.cause.stack).not.toContain('evalmachine');
608-
609-
const { json: runData } = await cliInspectJson(`runs ${run.runId}`);
610-
expect(runData.status).toBe('failed');
611-
}
612-
);
613-
614-
test(
615-
'cross-file imports preserve message and stack trace',
616-
{ timeout: 60_000 },
617-
async () => {
618-
const run = await triggerWorkflow('errorWorkflowCrossFile', []);
619-
const result = await getWorkflowReturnValue(run.runId);
620-
621-
expect(result.name).toBe('WorkflowRunFailedError');
622-
expect(result.cause.message).toContain(
623-
'Error from imported helper module'
624-
);
625-
expect(result.cause.stack).toContain('throwError');
626-
expect(result.cause.stack).toContain('callThrower');
627-
expect(result.cause.stack).toContain('errorWorkflowCrossFile');
628-
expect(result.cause.stack).not.toContain('evalmachine');
629-
630-
// helpers.ts reference (known issue: vite-based frameworks dev mode)
631-
const isViteBasedFrameworkDevMode =
632-
(process.env.APP_NAME === 'sveltekit' ||
633-
process.env.APP_NAME === 'vite' ||
634-
process.env.APP_NAME === 'astro') &&
635-
isLocalDeployment();
636-
if (!isViteBasedFrameworkDevMode) {
637-
expect(result.cause.stack).toContain('helpers.ts');
638-
}
639-
640-
const { json: runData } = await cliInspectJson(`runs ${run.runId}`);
641-
expect(runData.status).toBe('failed');
642-
}
643-
);
644-
});
588+
test('retryAttemptCounterWorkflow', { timeout: 60_000 }, async () => {
589+
const run = await triggerWorkflow('retryAttemptCounterWorkflow', []);
590+
const returnValue = await getWorkflowReturnValue(run.runId);
645591

646-
describe('step errors', () => {
647-
test(
648-
'basic step error preserves message',
649-
{ timeout: 60_000 },
650-
async () => {
651-
const run = await triggerWorkflow('errorStepBasic', []);
652-
const result = await getWorkflowReturnValue(run.runId);
653-
654-
expect(result.name).toBe('WorkflowRunFailedError');
655-
expect(result.cause.message).toContain('Step error message');
656-
657-
const { json: steps } = await cliInspectJson(
658-
`steps --runId ${run.runId}`
659-
);
660-
const failedStep = steps.find((s: any) =>
661-
s.stepName.includes('errorStepFn')
662-
);
663-
expect(failedStep.status).toBe('failed');
664-
665-
const { json: runData } = await cliInspectJson(`runs ${run.runId}`);
666-
expect(runData.status).toBe('failed');
667-
}
668-
);
592+
// The step should have succeeded on attempt 3
593+
expect(returnValue).toEqual({ finalAttempt: 3 });
669594

670-
test(
671-
'cross-file step error preserves message and function names in stack',
672-
{ timeout: 60_000 },
673-
async () => {
674-
const run = await triggerWorkflow('errorStepCrossFile', []);
675-
const result = await getWorkflowReturnValue(run.runId);
676-
677-
expect(result.name).toBe('WorkflowRunFailedError');
678-
expect(result.cause.message).toContain(
679-
'Step error from imported helper module'
680-
);
681-
682-
const { json: steps } = await cliInspectJson(
683-
`steps --runId ${run.runId}`
684-
);
685-
const failedStep = steps.find((s: any) =>
686-
s.stepName.includes('stepThatThrowsFromHelper')
687-
);
688-
expect(failedStep.status).toBe('failed');
689-
// Note: Step errors don't have source-mapped stack traces (known limitation)
690-
expect(failedStep.error.stack).toContain('throwErrorFromStep');
691-
expect(failedStep.error.stack).toContain(
692-
'stepThatThrowsFromHelper'
693-
);
694-
695-
const { json: runData } = await cliInspectJson(`runs ${run.runId}`);
696-
expect(runData.status).toBe('failed');
697-
}
698-
);
699-
});
595+
// Also verify the run data shows the correct output
596+
const { json: runData } = await cliInspectJson(
597+
`runs ${run.runId} --withData`
598+
);
599+
expect(runData).toMatchObject({
600+
runId: run.runId,
601+
status: 'completed',
602+
output: { finalAttempt: 3 },
700603
});
701604

702-
describe('retry behavior', () => {
703-
test(
704-
'regular Error retries until success',
705-
{ timeout: 60_000 },
706-
async () => {
707-
const run = await triggerWorkflow('errorRetrySuccess', []);
708-
const result = await getWorkflowReturnValue(run.runId);
709-
710-
expect(result.finalAttempt).toBe(3);
711-
712-
const { json: steps } = await cliInspectJson(
713-
`steps --runId ${run.runId}`
714-
);
715-
const step = steps.find((s: any) =>
716-
s.stepName.includes('retryUntilAttempt3')
717-
);
718-
expect(step.status).toBe('completed');
719-
expect(step.attempt).toBe(3);
720-
}
721-
);
605+
// Query steps separately to verify the step data
606+
const { json: stepsData } = await cliInspectJson(
607+
`steps --runId ${run.runId} --withData`
608+
);
609+
expect(stepsData).toBeDefined();
610+
expect(Array.isArray(stepsData)).toBe(true);
611+
expect(stepsData.length).toBeGreaterThan(0);
722612

723-
test(
724-
'FatalError fails immediately without retries',
725-
{ timeout: 60_000 },
726-
async () => {
727-
const run = await triggerWorkflow('errorRetryFatal', []);
728-
const result = await getWorkflowReturnValue(run.runId);
729-
730-
expect(result.name).toBe('WorkflowRunFailedError');
731-
expect(result.cause.message).toContain('Fatal step error');
732-
733-
const { json: steps } = await cliInspectJson(
734-
`steps --runId ${run.runId}`
735-
);
736-
const step = steps.find((s: any) =>
737-
s.stepName.includes('throwFatalError')
738-
);
739-
expect(step.status).toBe('failed');
740-
expect(step.attempt).toBe(1);
741-
}
742-
);
613+
// Find the stepThatRetriesAndSucceeds step
614+
const retryStep = stepsData.find((s: any) =>
615+
s.stepName.includes('stepThatRetriesAndSucceeds')
616+
);
617+
expect(retryStep).toBeDefined();
618+
expect(retryStep.status).toBe('completed');
619+
expect(retryStep.attempt).toBe(3);
620+
expect(retryStep.output).toEqual([3]);
621+
});
743622

744-
test(
745-
'RetryableError respects custom retryAfter delay',
746-
{ timeout: 60_000 },
747-
async () => {
748-
const run = await triggerWorkflow('errorRetryCustomDelay', []);
749-
const result = await getWorkflowReturnValue(run.runId);
623+
test('retryableAndFatalErrorWorkflow', { timeout: 60_000 }, async () => {
624+
const run = await triggerWorkflow('retryableAndFatalErrorWorkflow', []);
625+
const returnValue = await getWorkflowReturnValue(run.runId);
626+
expect(returnValue.retryableResult.attempt).toEqual(2);
627+
expect(returnValue.retryableResult.duration).toBeGreaterThan(10_000);
628+
expect(returnValue.gotFatalError).toBe(true);
629+
});
750630

751-
expect(result.attempt).toBe(2);
752-
expect(result.duration).toBeGreaterThan(10_000);
753-
}
754-
);
631+
test(
632+
'maxRetriesZeroWorkflow - maxRetries=0 runs once without retrying',
633+
{ timeout: 60_000 },
634+
async () => {
635+
const run = await triggerWorkflow('maxRetriesZeroWorkflow', []);
636+
const returnValue = await getWorkflowReturnValue(run.runId);
755637

756-
test('maxRetries=0 disables retries', { timeout: 60_000 }, async () => {
757-
const run = await triggerWorkflow('errorRetryDisabled', []);
758-
const result = await getWorkflowReturnValue(run.runId);
638+
// The step with maxRetries=0 that succeeds should have run on attempt 1
639+
expect(returnValue.successResult).toEqual({ attempt: 1 });
759640

760-
expect(result.failed).toBe(true);
761-
expect(result.attempt).toBe(1);
762-
});
763-
});
641+
// The step with maxRetries=0 that fails should have thrown an error
642+
expect(returnValue.gotError).toBe(true);
764643

765-
describe('catchability', () => {
766-
test(
767-
'FatalError can be caught and detected with FatalError.is()',
768-
{ timeout: 60_000 },
769-
async () => {
770-
const run = await triggerWorkflow('errorFatalCatchable', []);
771-
const result = await getWorkflowReturnValue(run.runId);
772-
773-
expect(result.caught).toBe(true);
774-
expect(result.isFatal).toBe(true);
775-
776-
// Verify workflow completed successfully (error was caught)
777-
const { json: runData } = await cliInspectJson(`runs ${run.runId}`);
778-
expect(runData.status).toBe('completed');
779-
}
780-
);
781-
});
782-
});
783-
// ==================== END ERROR HANDLING TESTS ====================
644+
// The failing step should have only run once (attempt 1), not retried
645+
expect(returnValue.failedAttempt).toBe(1);
646+
}
647+
);
784648

785649
test(
786650
'stepDirectCallWorkflow - calling step functions directly outside workflow context',
@@ -812,6 +676,68 @@ describe('e2e', () => {
812676
}
813677
);
814678

679+
test(
680+
'crossFileErrorWorkflow - stack traces work across imported modules',
681+
{ timeout: 60_000 },
682+
async () => {
683+
// This workflow intentionally throws an error from an imported helper module
684+
// to verify that stack traces correctly show cross-file call chains
685+
const run = await triggerWorkflow('crossFileErrorWorkflow', []);
686+
const returnValue = await getWorkflowReturnValue(run.runId);
687+
688+
// The workflow should fail with error response containing both top-level and cause
689+
expect(returnValue).toHaveProperty('name');
690+
expect(returnValue.name).toBe('WorkflowRunFailedError');
691+
expect(returnValue).toHaveProperty('message');
692+
693+
// Verify the cause property contains the structured error
694+
expect(returnValue).toHaveProperty('cause');
695+
expect(returnValue.cause).toBeTypeOf('object');
696+
expect(returnValue.cause).toHaveProperty('message');
697+
expect(returnValue.cause.message).toContain(
698+
'Error from imported helper module'
699+
);
700+
701+
// Verify the stack trace is present in the cause
702+
expect(returnValue.cause).toHaveProperty('stack');
703+
expect(typeof returnValue.cause.stack).toBe('string');
704+
705+
// Known issue: vite-based frameworks dev mode has incorrect source map mappings for bundled imports.
706+
// esbuild with bundle:true inlines helpers.ts but source maps incorrectly map to 99_e2e.ts
707+
// This works correctly in production and other frameworks.
708+
// TODO: Investigate esbuild source map generation for bundled modules
709+
const isViteBasedFrameworkDevMode =
710+
(process.env.APP_NAME === 'sveltekit' ||
711+
process.env.APP_NAME === 'vite' ||
712+
process.env.APP_NAME === 'astro') &&
713+
isLocalDeployment();
714+
715+
if (!isViteBasedFrameworkDevMode) {
716+
// Stack trace should include frames from the helper module (helpers.ts)
717+
expect(returnValue.cause.stack).toContain('helpers.ts');
718+
}
719+
720+
// These checks should work in all modes
721+
expect(returnValue.cause.stack).toContain('throwError');
722+
expect(returnValue.cause.stack).toContain('callThrower');
723+
724+
// Stack trace should include frames from the workflow file (99_e2e.ts)
725+
expect(returnValue.cause.stack).toContain('99_e2e.ts');
726+
expect(returnValue.cause.stack).toContain('crossFileErrorWorkflow');
727+
728+
// Stack trace should NOT contain 'evalmachine' anywhere
729+
expect(returnValue.cause.stack).not.toContain('evalmachine');
730+
731+
// Verify the run failed with structured error
732+
const { json: runData } = await cliInspectJson(`runs ${run.runId}`);
733+
expect(runData.status).toBe('failed');
734+
expect(runData.error).toBeTypeOf('object');
735+
expect(runData.error.message).toContain(
736+
'Error from imported helper module'
737+
);
738+
}
739+
);
740+
815741
test(
816742
'hookCleanupTestWorkflow - hook token reuse after workflow completion',
817743
{ timeout: 60_000 },

0 commit comments

Comments
 (0)