From 733095915df66b326a1af272eef223852c188e87 Mon Sep 17 00:00:00 2001 From: Jesse Turner Date: Wed, 27 May 2026 13:22:57 -0400 Subject: [PATCH 1/3] fix(deploy): add harness teardown to TUI deploy flow The TUI deploy path (useDeployFlow.ts) was missing the imperative harness deployment and teardown logic that exists in the CLI deploy path (actions.ts). This caused "remove all and deploy" to skip harness deletion when run through the TUI. Add post-CDK harness deploy during normal deploys so harness state is persisted to deployed-state.json, and add imperative teardown before stack destroy during teardown deploys. Constraint: Must be gated behind isPreviewEnabled() to match CLI path Rejected: Ungated harness deploy | would expose harness features in GA builds Confidence: high Scope-risk: narrow --- e2e-tests/harness-remove-all.test.ts | 167 ++++++++++++++++++++ src/cli/tui/screens/deploy/useDeployFlow.ts | 67 ++++++++ 2 files changed, 234 insertions(+) create mode 100644 e2e-tests/harness-remove-all.test.ts diff --git a/e2e-tests/harness-remove-all.test.ts b/e2e-tests/harness-remove-all.test.ts new file mode 100644 index 000000000..d45200e15 --- /dev/null +++ b/e2e-tests/harness-remove-all.test.ts @@ -0,0 +1,167 @@ +import { getHarness } from '../src/cli/aws/agentcore-harness.js'; +import { hasAwsCredentials, parseJsonOutput, prereqs, retry } from '../src/test-utils/index.js'; +import { installCdkTarball, runAgentCoreCLI, writeAwsTargets } from './e2e-helper.js'; +import { randomUUID } from 'node:crypto'; +import { mkdir, readFile, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const hasAws = hasAwsCredentials(); +const isPreviewBuild = process.env.BUILD_PREVIEW === '1'; +const canRun = prereqs.npm && prereqs.git && hasAws && isPreviewBuild; + +describe.sequential('e2e: harness remove-all teardown — create → deploy → remove all → deploy → verify deleted', () => { + let testDir: string; + let projectPath: string; + let harnessName: string; + let harnessId: string; + let region: string; + + beforeAll(async () => { + if (!canRun) return; + + testDir = join(tmpdir(), `agentcore-e2e-harness-teardown-${randomUUID()}`); + await mkdir(testDir, { recursive: true }); + + harnessName = `E2eTrdn${String(Date.now()).slice(-8)}`; + region = process.env.AWS_REGION ?? 'us-east-1'; + + const createArgs = [ + 'create', + '--name', + harnessName, + '--model-provider', + 'bedrock', + '--json', + '--skip-git', + '--no-harness-memory', + ]; + + const result = await runAgentCoreCLI(createArgs, testDir); + expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0); + + const json = parseJsonOutput(result.stdout) as { projectPath: string }; + projectPath = json.projectPath; + + await writeAwsTargets(projectPath); + installCdkTarball(projectPath); + }, 300000); + + afterAll(async () => { + if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 }); + }, 60000); + + it.skipIf(!canRun)( + 'deploys harness to AWS successfully', + async () => { + expect(projectPath, 'Project should have been created').toBeTruthy(); + + await retry( + async () => { + const result = await runAgentCoreCLI(['deploy', '--yes', '--json'], projectPath); + + if (result.exitCode !== 0) { + console.log('Deploy stdout:', result.stdout); + console.log('Deploy stderr:', result.stderr); + } + + expect(result.exitCode, `Deploy failed: ${result.stderr}`).toBe(0); + + const json = parseJsonOutput(result.stdout) as { success: boolean }; + expect(json.success, 'Deploy should report success').toBe(true); + }, + 1, + 30000 + ); + }, + 600000 + ); + + it.skipIf(!canRun)( + 'verifies harness exists in AWS with READY status', + async () => { + const statePath = join(projectPath, 'agentcore', '.cli', 'deployed-state.json'); + const stateJson = JSON.parse(await readFile(statePath, 'utf-8')); + const harnesses = stateJson.targets?.default?.resources?.harnesses; + + expect(harnesses, 'deployed-state.json should have harnesses').toBeDefined(); + expect(harnesses[harnessName], `Harness "${harnessName}" should be in deployed state`).toBeDefined(); + + harnessId = harnesses[harnessName].harnessId; + expect(harnessId, 'harnessId should be set').toBeTruthy(); + + await retry( + async () => { + const result = await getHarness({ region, harnessId }); + expect(result.harness.status).toBe('READY'); + }, + 3, + 5000 + ); + }, + 120000 + ); + + it.skipIf(!canRun)( + 'runs remove all successfully', + async () => { + const result = await runAgentCoreCLI(['remove', 'all', '--yes', '--json'], projectPath); + expect(result.exitCode, `Remove all failed: ${result.stderr}`).toBe(0); + + const json = parseJsonOutput(result.stdout) as { success: boolean }; + expect(json.success).toBe(true); + + const configPath = join(projectPath, 'agentcore', 'agentcore.json'); + const config = JSON.parse(await readFile(configPath, 'utf-8')); + expect(config.harnesses).toEqual([]); + }, + 60000 + ); + + it.skipIf(!canRun)( + 'deploys (teardown) successfully', + async () => { + const result = await runAgentCoreCLI(['deploy', '--yes', '--json'], projectPath); + + if (result.exitCode !== 0) { + console.log('Teardown deploy stdout:', result.stdout); + console.log('Teardown deploy stderr:', result.stderr); + } + + expect(result.exitCode, `Teardown deploy failed: ${result.stderr}`).toBe(0); + + const json = parseJsonOutput(result.stdout) as { success: boolean }; + expect(json.success, 'Teardown deploy should report success').toBe(true); + }, + 600000 + ); + + it.skipIf(!canRun)( + 'verifies harness is deleted from AWS', + async () => { + expect(harnessId, 'harnessId should have been captured from deploy step').toBeTruthy(); + + await retry( + async () => { + try { + const result = await getHarness({ region, harnessId }); + expect( + ['DELETING', 'DELETED'], + `Expected harness status to be DELETING or DELETED, got ${result.harness.status}` + ).toContain(result.harness.status); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + expect( + message.includes('not found') || message.includes('ResourceNotFoundException'), + `Expected ResourceNotFound error, got: ${message}` + ).toBe(true); + } + }, + 5, + 10000 + ); + }, + 120000 + ); +}); diff --git a/src/cli/tui/screens/deploy/useDeployFlow.ts b/src/cli/tui/screens/deploy/useDeployFlow.ts index 8b0295744..5f895e2b5 100644 --- a/src/cli/tui/screens/deploy/useDeployFlow.ts +++ b/src/cli/tui/screens/deploy/useDeployFlow.ts @@ -1,4 +1,5 @@ import { ConfigIO } from '../../../../lib'; +import type { DeployedState, HarnessDeployedState } from '../../../../schema'; import type { CdkToolkitWrapper, DeployMessage, SwitchableIoHost } from '../../../cdk/toolkit-lib'; import { buildDeployedState, @@ -14,9 +15,11 @@ import { } from '../../../cloudformation'; import { DEFAULT_DEPLOY_ATTRS, computeDeployAttrs } from '../../../commands/deploy/utils.js'; import { getErrorMessage, isChangesetInProgressError, isExpiredTokenError } from '../../../errors'; +import { isPreviewEnabled } from '../../../feature-flags'; import { ExecLogger } from '../../../logging'; import { performStackTeardown, setupTransactionSearch } from '../../../operations/deploy'; import { getGatewayTargetStatuses } from '../../../operations/deploy/gateway-status'; +import { createDeploymentManager } from '../../../operations/deploy/imperative'; import { deleteOrphanedABTests, setupABTests } from '../../../operations/deploy/post-deploy-ab-tests'; import { resolveConfigBundleComponentKeys, @@ -319,6 +322,38 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState setStackOutputs(outputs); const existingState = await configIO.readDeployedState().catch(() => undefined); + + // Post-CDK: deploy imperative resources (harness) — preview mode only + let deployedHarnesses: Record | undefined; + if (isPreviewEnabled()) { + const imperativeManager = createDeploymentManager(); + const imperativeDeployedState: DeployedState = existingState ?? { targets: {} }; + const imperativeContext = { + projectSpec: ctx.projectSpec, + target, + configIO, + deployedState: imperativeDeployedState, + cdkOutputs: outputs, + onProgress: (step: string, status: 'start' | 'done' | 'error') => { + logger.log(`${step}: ${status}`); + }, + }; + + if (imperativeManager.hasDeployersForPhase('post-cdk', imperativeContext)) { + logger.startStep('Deploy harnesses'); + const postCdkResult = await imperativeManager.runPhase('post-cdk', imperativeContext); + const harnessResult = postCdkResult.results.get('harness'); + if (harnessResult?.state) { + deployedHarnesses = harnessResult.state as Record; + } + if (!postCdkResult.success) { + logger.endStep('error', postCdkResult.error); + throw new Error(`Harness deployment failed: ${postCdkResult.error}`); + } + logger.endStep('success'); + } + } + let deployedState = buildDeployedState({ targetName: target.name, stackName: currentStackName, @@ -333,6 +368,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState policyEngines, policies, datasets, + harnesses: deployedHarnesses, }); await configIO.writeDeployedState(deployedState); @@ -673,6 +709,37 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState await cdkToolkitWrapper.deploy(); if (context?.isTeardownDeploy) { + // Teardown imperative resources (harnesses) before destroying the stack + if (isPreviewEnabled()) { + const teardownTarget = context.awsTargets[0]; + if (teardownTarget) { + const imperativeManager = createDeploymentManager(); + const teardownConfigIO = new ConfigIO(); + const existingTeardownState = await teardownConfigIO + .readDeployedState() + .catch(() => ({ targets: {} }) as DeployedState); + const teardownContext = { + projectSpec: context.projectSpec, + target: teardownTarget, + configIO: teardownConfigIO, + deployedState: existingTeardownState, + onProgress: (step: string, status: 'start' | 'done' | 'error') => { + logger.log(`${step}: ${status}`); + }, + }; + + if (imperativeManager.hasDeployersForPhase('post-cdk', teardownContext)) { + logger.startStep('Tear down imperative resources'); + const teardownResult = await imperativeManager.teardownAll(teardownContext); + if (!teardownResult.success) { + logger.endStep('error', teardownResult.error); + throw new Error(`Imperative teardown failed: ${teardownResult.error}`); + } + logger.endStep('success'); + } + } + } + // After deploying the empty spec, destroy the stack entirely const targetName = context.awsTargets[0]?.name; if (targetName) { From 4c7f4f29694ecf686e90df2b2368e7e9244bfca9 Mon Sep 17 00:00:00 2001 From: Jesse Turner Date: Wed, 27 May 2026 13:59:34 -0400 Subject: [PATCH 2/3] refactor(e2e): fold harness teardown test into shared suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the remove-all → deploy → verify-deleted assertions from the standalone harness-remove-all.test.ts into createHarnessE2ESuite so all harness e2e tests (bedrock, gemini, openai) verify teardown, saving an entire deploy cycle in CI. Rejected: Keep separate test file | wastes ~10min CI time duplicating deploy Confidence: high Scope-risk: narrow --- e2e-tests/harness-e2e-helper.ts | 65 ++++++++++- e2e-tests/harness-remove-all.test.ts | 167 --------------------------- 2 files changed, 62 insertions(+), 170 deletions(-) delete mode 100644 e2e-tests/harness-remove-all.test.ts diff --git a/e2e-tests/harness-e2e-helper.ts b/e2e-tests/harness-e2e-helper.ts index a0ee882c9..b10e173dc 100644 --- a/e2e-tests/harness-e2e-helper.ts +++ b/e2e-tests/harness-e2e-helper.ts @@ -1,3 +1,4 @@ +import { getHarness } from '../src/cli/aws/agentcore-harness.js'; import { hasAwsCredentials, parseJsonOutput, prereqs, retry, spawnAndCollect } from '../src/test-utils/index.js'; import { cleanupStaleCredentialProviders, @@ -7,7 +8,7 @@ import { writeAwsTargets, } from './e2e-helper.js'; import { randomUUID } from 'node:crypto'; -import { mkdir, rm } from 'node:fs/promises'; +import { mkdir, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; @@ -30,10 +31,11 @@ export function createHarnessE2ESuite(cfg: HarnessE2EConfig) { const providerLabel = cfg.modelProvider === 'open_ai' ? 'OpenAI' : cfg.modelProvider === 'gemini' ? 'Gemini' : 'Bedrock'; - describe.sequential(`e2e: harness/${providerLabel} — create → deploy → invoke`, () => { + describe.sequential(`e2e: harness/${providerLabel} — create → deploy → invoke → teardown`, () => { let testDir: string; let projectPath: string; let harnessName: string; + let harnessId: string; beforeAll(async () => { if (!canRun) return; @@ -76,7 +78,8 @@ export function createHarnessE2ESuite(cfg: HarnessE2EConfig) { afterAll(async () => { if (projectPath && hasAws) { - await teardownE2EProject(projectPath, harnessName, cfg.modelProvider); + // Teardown is tested as a step; this is a safety net in case earlier steps fail + await teardownE2EProject(projectPath, harnessName, cfg.modelProvider).catch((_: unknown) => undefined); } if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 }); }, 600000); @@ -158,6 +161,62 @@ export function createHarnessE2ESuite(cfg: HarnessE2EConfig) { expect(harness, `Harness "${harnessName}" should appear in status`).toBeDefined(); expect(harness!.deploymentState).toBe('deployed'); expect(harness!.identifier, 'Deployed harness should have a harnessArn').toBeTruthy(); + + // Capture harnessId for teardown verification + const statePath = join(projectPath, 'agentcore', '.cli', 'deployed-state.json'); + const stateJson = JSON.parse(await readFile(statePath, 'utf-8')) as { + targets?: { default?: { resources?: { harnesses?: Record } } }; + }; + const harnessEntry = stateJson.targets?.default?.resources?.harnesses?.[harnessName]; + if (harnessEntry) { + harnessId = harnessEntry.harnessId; + } + }, + 120000 + ); + + it.skipIf(!canRun)( + 'remove all and deploy tears down harness', + async () => { + const removeResult = await runAgentCoreCLI(['remove', 'all', '--yes', '--json'], projectPath); + expect(removeResult.exitCode, `Remove all failed: ${removeResult.stderr}`).toBe(0); + + const removeJson = parseJsonOutput(removeResult.stdout) as { success: boolean }; + expect(removeJson.success).toBe(true); + + const deployResult = await runAgentCoreCLI(['deploy', '--yes', '--json'], projectPath); + expect(deployResult.exitCode, `Teardown deploy failed: ${deployResult.stderr}`).toBe(0); + + const deployJson = parseJsonOutput(deployResult.stdout) as { success: boolean }; + expect(deployJson.success).toBe(true); + }, + 600000 + ); + + it.skipIf(!canRun)( + 'verifies harness is deleted from AWS', + async () => { + expect(harnessId, 'harnessId should have been captured').toBeTruthy(); + + const region = process.env.AWS_REGION ?? 'us-east-1'; + await retry( + async () => { + try { + const result = await getHarness({ region, harnessId }); + expect(['DELETING', 'DELETED'], `Expected DELETING or DELETED, got ${result.harness.status}`).toContain( + result.harness.status + ); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + expect( + message.includes('not found') || message.includes('ResourceNotFoundException'), + `Expected ResourceNotFound, got: ${message}` + ).toBe(true); + } + }, + 5, + 10000 + ); }, 120000 ); diff --git a/e2e-tests/harness-remove-all.test.ts b/e2e-tests/harness-remove-all.test.ts deleted file mode 100644 index d45200e15..000000000 --- a/e2e-tests/harness-remove-all.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { getHarness } from '../src/cli/aws/agentcore-harness.js'; -import { hasAwsCredentials, parseJsonOutput, prereqs, retry } from '../src/test-utils/index.js'; -import { installCdkTarball, runAgentCoreCLI, writeAwsTargets } from './e2e-helper.js'; -import { randomUUID } from 'node:crypto'; -import { mkdir, readFile, rm } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const hasAws = hasAwsCredentials(); -const isPreviewBuild = process.env.BUILD_PREVIEW === '1'; -const canRun = prereqs.npm && prereqs.git && hasAws && isPreviewBuild; - -describe.sequential('e2e: harness remove-all teardown — create → deploy → remove all → deploy → verify deleted', () => { - let testDir: string; - let projectPath: string; - let harnessName: string; - let harnessId: string; - let region: string; - - beforeAll(async () => { - if (!canRun) return; - - testDir = join(tmpdir(), `agentcore-e2e-harness-teardown-${randomUUID()}`); - await mkdir(testDir, { recursive: true }); - - harnessName = `E2eTrdn${String(Date.now()).slice(-8)}`; - region = process.env.AWS_REGION ?? 'us-east-1'; - - const createArgs = [ - 'create', - '--name', - harnessName, - '--model-provider', - 'bedrock', - '--json', - '--skip-git', - '--no-harness-memory', - ]; - - const result = await runAgentCoreCLI(createArgs, testDir); - expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0); - - const json = parseJsonOutput(result.stdout) as { projectPath: string }; - projectPath = json.projectPath; - - await writeAwsTargets(projectPath); - installCdkTarball(projectPath); - }, 300000); - - afterAll(async () => { - if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 }); - }, 60000); - - it.skipIf(!canRun)( - 'deploys harness to AWS successfully', - async () => { - expect(projectPath, 'Project should have been created').toBeTruthy(); - - await retry( - async () => { - const result = await runAgentCoreCLI(['deploy', '--yes', '--json'], projectPath); - - if (result.exitCode !== 0) { - console.log('Deploy stdout:', result.stdout); - console.log('Deploy stderr:', result.stderr); - } - - expect(result.exitCode, `Deploy failed: ${result.stderr}`).toBe(0); - - const json = parseJsonOutput(result.stdout) as { success: boolean }; - expect(json.success, 'Deploy should report success').toBe(true); - }, - 1, - 30000 - ); - }, - 600000 - ); - - it.skipIf(!canRun)( - 'verifies harness exists in AWS with READY status', - async () => { - const statePath = join(projectPath, 'agentcore', '.cli', 'deployed-state.json'); - const stateJson = JSON.parse(await readFile(statePath, 'utf-8')); - const harnesses = stateJson.targets?.default?.resources?.harnesses; - - expect(harnesses, 'deployed-state.json should have harnesses').toBeDefined(); - expect(harnesses[harnessName], `Harness "${harnessName}" should be in deployed state`).toBeDefined(); - - harnessId = harnesses[harnessName].harnessId; - expect(harnessId, 'harnessId should be set').toBeTruthy(); - - await retry( - async () => { - const result = await getHarness({ region, harnessId }); - expect(result.harness.status).toBe('READY'); - }, - 3, - 5000 - ); - }, - 120000 - ); - - it.skipIf(!canRun)( - 'runs remove all successfully', - async () => { - const result = await runAgentCoreCLI(['remove', 'all', '--yes', '--json'], projectPath); - expect(result.exitCode, `Remove all failed: ${result.stderr}`).toBe(0); - - const json = parseJsonOutput(result.stdout) as { success: boolean }; - expect(json.success).toBe(true); - - const configPath = join(projectPath, 'agentcore', 'agentcore.json'); - const config = JSON.parse(await readFile(configPath, 'utf-8')); - expect(config.harnesses).toEqual([]); - }, - 60000 - ); - - it.skipIf(!canRun)( - 'deploys (teardown) successfully', - async () => { - const result = await runAgentCoreCLI(['deploy', '--yes', '--json'], projectPath); - - if (result.exitCode !== 0) { - console.log('Teardown deploy stdout:', result.stdout); - console.log('Teardown deploy stderr:', result.stderr); - } - - expect(result.exitCode, `Teardown deploy failed: ${result.stderr}`).toBe(0); - - const json = parseJsonOutput(result.stdout) as { success: boolean }; - expect(json.success, 'Teardown deploy should report success').toBe(true); - }, - 600000 - ); - - it.skipIf(!canRun)( - 'verifies harness is deleted from AWS', - async () => { - expect(harnessId, 'harnessId should have been captured from deploy step').toBeTruthy(); - - await retry( - async () => { - try { - const result = await getHarness({ region, harnessId }); - expect( - ['DELETING', 'DELETED'], - `Expected harness status to be DELETING or DELETED, got ${result.harness.status}` - ).toContain(result.harness.status); - } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); - expect( - message.includes('not found') || message.includes('ResourceNotFoundException'), - `Expected ResourceNotFound error, got: ${message}` - ).toBe(true); - } - }, - 5, - 10000 - ); - }, - 120000 - ); -}); From 61a5f2cd7f70c183e41e9f369f43f29ddfb74b3c Mon Sep 17 00:00:00 2001 From: Jesse Turner Date: Wed, 27 May 2026 14:14:25 -0400 Subject: [PATCH 3/3] fix(ci): use bundle script for e2e and run harness tests with preview build - Replace separate build+CDK steps with `npm run bundle` which produces both GA and preview tarballs with CDK embedded - GA e2e: install GA tarball, run strands-bedrock baseline + extras - Preview e2e: install preview tarball, run harness-bedrock baseline + extras - Detect helper file changes to trigger all e2e tests Confidence: high Scope-risk: moderate --- .github/workflows/e2e-tests.yml | 76 ++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 05ff6bee3..6f5a46e85 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -86,44 +86,78 @@ jobs: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} owner: aws - # Build @aws/agentcore-cdk from source for cross-package testing. - # Requires secret: CDK_REPO_NAME (org/repo). Token is generated by the App above. - - name: Build CDK package + # Clone CDK repo for bundle script (requires App token for private repo access) + - name: Clone CDK repo run: | CDK_BRANCH="${{ inputs.cdk_branch || 'main' }}" - echo "Building CDK from branch: $CDK_BRANCH" + echo "Cloning CDK from branch: $CDK_BRANCH" git clone --depth 1 --branch "$CDK_BRANCH" "https://x-access-token:${CDK_REPO_TOKEN}@github.com/${CDK_REPO}.git" /tmp/cdk-repo - cd /tmp/cdk-repo - npm ci - npm run build - TARBALL=$(npm pack --pack-destination "$RUNNER_TEMP" | tail -1) - echo "CDK_TARBALL=$RUNNER_TEMP/$TARBALL" >> "$GITHUB_ENV" env: CDK_REPO_TOKEN: ${{ steps.app-token.outputs.token }} CDK_REPO: ${{ secrets.CDK_REPO_NAME }} - run: npm ci - - run: npm run build - - name: Install CLI globally - run: npm install -g "$(npm pack | tail -1)" + + - name: Bundle GA and preview tarballs + run: | + npm run bundle + GA_TARBALL=$(ls aws-agentcore-*.tgz | grep -v preview | head -1) + PREVIEW_TARBALL=$(ls aws-agentcore-*-preview-*.tgz | head -1) + echo "GA_TARBALL=$PWD/$GA_TARBALL" >> "$GITHUB_ENV" + echo "PREVIEW_TARBALL=$PWD/$PREVIEW_TARBALL" >> "$GITHUB_ENV" + env: + AGENTCORE_CDK_PATH: /tmp/cdk-repo + + - name: Install GA CLI globally + run: npm install -g "$GA_TARBALL" - name: Detect changed e2e test files id: changed run: | BASE_SHA=${{ github.event.pull_request.base.sha || 'HEAD~1' }} - CHANGED=$(git diff --name-only "$BASE_SHA"..HEAD -- 'e2e-tests/*.test.ts' \ - | grep -v '^e2e-tests/strands-bedrock\.test\.ts$' \ - | tr '\n' ' ') - echo "extra_tests=$CHANGED" >> "$GITHUB_OUTPUT" - echo "Changed e2e tests: ${CHANGED:-none}" + # If any helper file changed, run all e2e tests + HELPERS_CHANGED=$(git diff --name-only "$BASE_SHA"..HEAD -- 'e2e-tests/*.ts' \ + | grep -v '\.test\.ts$' | head -1) + if [ -n "$HELPERS_CHANGED" ]; then + GA_EXTRA=$(find e2e-tests -name '*.test.ts' \ + | grep -v '^e2e-tests/strands-bedrock\.test\.ts$' \ + | grep -v '^e2e-tests/harness-' \ + | tr '\n' ' ') + HARNESS_EXTRA=$(find e2e-tests -name 'harness-*.test.ts' \ + | grep -v '^e2e-tests/harness-bedrock\.test\.ts$' \ + | tr '\n' ' ') + else + GA_EXTRA=$(git diff --name-only "$BASE_SHA"..HEAD -- 'e2e-tests/*.test.ts' \ + | grep -v '^e2e-tests/strands-bedrock\.test\.ts$' \ + | grep -v '^e2e-tests/harness-' \ + | tr '\n' ' ') + HARNESS_EXTRA=$(git diff --name-only "$BASE_SHA"..HEAD -- 'e2e-tests/harness-*.test.ts' \ + | grep -v '^e2e-tests/harness-bedrock\.test\.ts$' \ + | tr '\n' ' ') + fi + echo "ga_extra=$GA_EXTRA" >> "$GITHUB_OUTPUT" + echo "harness_extra=$HARNESS_EXTRA" >> "$GITHUB_OUTPUT" + echo "GA extra tests: ${GA_EXTRA:-none}" + echo "Harness extra tests: ${HARNESS_EXTRA:-none}" + + - name: Run E2E tests (GA) + env: + AWS_ACCOUNT_ID: ${{ steps.aws.outputs.account_id }} + AWS_REGION: ${{ inputs.aws_region || 'us-east-1' }} + ANTHROPIC_API_KEY: ${{ env.E2E_ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ env.E2E_OPENAI_API_KEY }} + GEMINI_API_KEY: ${{ env.E2E_GEMINI_API_KEY }} + run: npx vitest run --project e2e e2e-tests/strands-bedrock.test.ts ${{ steps.changed.outputs.ga_extra }} + + - name: Install preview CLI globally + run: npm install -g "$PREVIEW_TARBALL" - - name: Run E2E tests + - name: Run E2E tests (preview/harness) env: AWS_ACCOUNT_ID: ${{ steps.aws.outputs.account_id }} AWS_REGION: ${{ inputs.aws_region || 'us-east-1' }} ANTHROPIC_API_KEY: ${{ env.E2E_ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ env.E2E_OPENAI_API_KEY }} GEMINI_API_KEY: ${{ env.E2E_GEMINI_API_KEY }} - CDK_TARBALL: ${{ env.CDK_TARBALL }} - # Always run strands-bedrock as baseline, plus any e2e test files changed in the PR - run: npx vitest run --project e2e e2e-tests/strands-bedrock.test.ts ${{ steps.changed.outputs.extra_tests }} + BUILD_PREVIEW: '1' + run: npx vitest run --project e2e e2e-tests/harness-bedrock.test.ts ${{ steps.changed.outputs.harness_extra }}