') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Test e2e trigger by jesseturner21 · Pull Request #345 · aws/agentcore-cli · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,10 +38,19 @@ jobs:
- name: Get AWS Account ID
id: aws
run: echo "account_id=$(aws sts get-caller-identity --query Account --output text)" >> "$GITHUB_OUTPUT"
- name: Get API keys from Secrets Manager
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
e2e,arn:aws:secretsmanager:us-east-1:685197708687:secret:e2e-api-keys-jRHRJ5
parse-json-secrets: true
- run: npm ci
- run: npm run build
- name: Run E2E tests
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: npm run test:e2e
112 changes: 0 additions & 112 deletions e2e-tests/create-deploy-invoke.test.ts

This file was deleted.

152 changes: 152 additions & 0 deletions e2e-tests/e2e-helper.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
import { hasAwsCredentials, parseJsonOutput, prereqs, runCLI } from '../src/test-utils/index.js';
import { execSync } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { mkdir, rm, writeFile } 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 baseCanRun = prereqs.npm && prereqs.git && prereqs.uv && hasAws;

interface E2EConfig {
framework: string;
modelProvider: string;
requiredEnvVar?: string;
}

/**
* Retry an async function up to `times` attempts with a delay between retries.
*/
async function retry<T>(fn: () => Promise<T>, times: number, delayMs: number): Promise<T> {
let lastError: unknown;
for (let i = 0; i < times; i++) {
try {
return await fn();
} catch (err) {
lastError = err;
if (i < times - 1) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
}
throw lastError;
}

export function createE2ESuite(cfg: E2EConfig) {
const hasApiKey = !cfg.requiredEnvVar || !!process.env[cfg.requiredEnvVar];
const canRun = baseCanRun && hasApiKey;

describe.sequential(`e2e: ${cfg.framework}/${cfg.modelProvider} — create → deploy → invoke`, () => {
let testDir: string;
let projectPath: string;
let agentName: string;

beforeAll(async () => {
if (!canRun) return;

testDir = join(tmpdir(), `agentcore-e2e-${randomUUID()}`);
await mkdir(testDir, { recursive: true });

agentName = `E2e${cfg.framework.slice(0, 4)}${cfg.modelProvider.slice(0, 4)}${String(Date.now()).slice(-8)}`;
const createArgs = [
'create',
'--name',
agentName,
'--language',
'Python',
'--framework',
cfg.framework,
'--model-provider',
cfg.modelProvider,
'--memory',
'none',
'--json',
];

// Pass API key so the credential is registered in the project and .env.local
const apiKey = cfg.requiredEnvVar ? process.env[cfg.requiredEnvVar] : undefined;
if (apiKey) {
createArgs.push('--api-key', apiKey);
}

const result = await runCLI(createArgs, testDir, false);

expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0);
const json = parseJsonOutput(result.stdout) as { projectPath: string };
projectPath = json.projectPath;

// TODO: Replace with `agentcore add target` once the CLI command is re-introduced
const account =
process.env.AWS_ACCOUNT_ID ??
execSync('aws sts get-caller-identity --query Account --output text').toString().trim();
const region = process.env.AWS_REGION ?? 'us-east-1';
const awsTargetsPath = join(projectPath, 'agentcore', 'aws-targets.json');
await writeFile(awsTargetsPath, JSON.stringify([{ name: 'default', account, region }]));
}, 300000);

afterAll(async () => {
if (projectPath && hasAws) {
await runCLI(['remove', 'all', '--json'], projectPath, false);
const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false);

if (result.exitCode !== 0) {
console.log('Teardown stdout:', result.stdout);
console.log('Teardown stderr:', result.stderr);
}
}
if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 });
}, 600000);

it.skipIf(!canRun)(
'deploys to AWS successfully',
async () => {
expect(projectPath, 'Project should have been created').toBeTruthy();

const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false);

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);
},
600000
);

it.skipIf(!canRun)(
'invokes the deployed agent',
async () => {
expect(projectPath, 'Project should have been created').toBeTruthy();

// Retry invoke to handle cold-start / runtime initialization delays
await retry(
async () => {
const result = await runCLI(
['invoke', '--prompt', 'Say hello', '--agent', agentName, '--json'],
projectPath,
false
);

if (result.exitCode !== 0) {
console.log('Invoke stdout:', result.stdout);
console.log('Invoke stderr:', result.stderr);
}

expect(result.exitCode, `Invoke failed: ${result.stderr}`).toBe(0);

const json = parseJsonOutput(result.stdout) as { success: boolean };
expect(json.success, 'Invoke should report success').toBe(true);
},
3,
15000
);
},
180000
);
});
}
3 changes: 3 additions & 0 deletions e2e-tests/googleadk-gemini.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'GoogleADK', modelProvider: 'Gemini', requiredEnvVar: 'GEMINI_API_KEY' });
3 changes: 3 additions & 0 deletions e2e-tests/langgraph-anthropic.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'LangChain_LangGraph', modelProvider: 'Anthropic', requiredEnvVar: 'ANTHROPIC_API_KEY' });
3 changes: 3 additions & 0 deletions e2e-tests/langgraph-bedrock.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'LangChain_LangGraph', modelProvider: 'Bedrock' });
3 changes: 3 additions & 0 deletions e2e-tests/langgraph-gemini.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'LangChain_LangGraph', modelProvider: 'Gemini', requiredEnvVar: 'GEMINI_API_KEY' });
3 changes: 3 additions & 0 deletions e2e-tests/langgraph-openai.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'LangChain_LangGraph', modelProvider: 'OpenAI', requiredEnvVar: 'OPENAI_API_KEY' });
3 changes: 3 additions & 0 deletions e2e-tests/openaiagents-openai.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'OpenAIAgents', modelProvider: 'OpenAI', requiredEnvVar: 'OPENAI_API_KEY' });
3 changes: 3 additions & 0 deletions e2e-tests/strands-anthropic.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'Strands', modelProvider: 'Anthropic', requiredEnvVar: 'ANTHROPIC_API_KEY' });
3 changes: 3 additions & 0 deletions e2e-tests/strands-bedrock.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'Strands', modelProvider: 'Bedrock' });
3 changes: 3 additions & 0 deletions e2e-tests/strands-gemini.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'Strands', modelProvider: 'Gemini', requiredEnvVar: 'GEMINI_API_KEY' });
3 changes: 3 additions & 0 deletions e2e-tests/strands-openai.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
import { createE2ESuite } from './e2e-helper.js';

createE2ESuite({ framework: 'Strands', modelProvider: 'OpenAI', requiredEnvVar: 'OPENAI_API_KEY' });
Loading
Loading