Skip to content

Commit bf47f4d

Browse files
committed
test(e2e): add parallelism, filter test suites by changes, up retries
Signed-off-by: Tyler Slaton <[email protected]>
1 parent af8b90f commit bf47f4d

File tree

4 files changed

+405
-238
lines changed

4 files changed

+405
-238
lines changed

.github/workflows/dojo-e2e.yml

Lines changed: 179 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,142 @@ on:
77
branches: [main]
88

99
jobs:
10+
changes:
11+
name: Determine suites to run
12+
runs-on: ubuntu-latest
13+
outputs:
14+
matrix: ${{ steps.set-matrix.outputs.matrix }}
15+
should_run: ${{ steps.set-matrix.outputs.should_run }}
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- name: Detect changed areas
23+
id: filter
24+
uses: dorny/paths-filter@v3
25+
with:
26+
filters: |
27+
core_ts:
28+
- 'typescript-sdk/packages/**'
29+
- 'typescript-sdk/package.json'
30+
- 'typescript-sdk/pnpm-lock.yaml'
31+
- 'typescript-sdk/pnpm-workspace.yaml'
32+
- 'typescript-sdk/tsconfig.json'
33+
- 'typescript-sdk/turbo.json'
34+
core_py:
35+
- 'python-sdk/**'
36+
e2e_tests:
37+
- 'typescript-sdk/apps/dojo/e2e/**'
38+
- 'typescript-sdk/apps/dojo/scripts/**'
39+
workflow_self:
40+
- '.github/workflows/dojo-e2e.yml'
41+
agno:
42+
- 'typescript-sdk/integrations/agno/**'
43+
crew_ai:
44+
- 'typescript-sdk/integrations/crewai/**'
45+
langgraph:
46+
- 'typescript-sdk/integrations/langgraph/**'
47+
llama_index:
48+
- 'typescript-sdk/integrations/llamaindex/**'
49+
mastra:
50+
- 'typescript-sdk/integrations/mastra/**'
51+
middleware_starter:
52+
- 'typescript-sdk/integrations/middleware-starter/**'
53+
pydantic_ai:
54+
- 'typescript-sdk/integrations/pydantic-ai/**'
55+
server_starter:
56+
- 'typescript-sdk/integrations/server-starter/**'
57+
server_starter_all:
58+
- 'typescript-sdk/integrations/server-starter-all-features/**'
59+
vercel_ai_sdk:
60+
- 'typescript-sdk/integrations/vercel-ai-sdk/**'
61+
62+
- name: Build dynamic matrix
63+
id: set-matrix
64+
env:
65+
CORE_TS: ${{ steps.filter.outputs.core_ts }}
66+
CORE_PY: ${{ steps.filter.outputs.core_py }}
67+
E2E_TESTS: ${{ steps.filter.outputs.e2e_tests }}
68+
WORKFLOW_SELF: ${{ steps.filter.outputs.workflow_self }}
69+
AGNO: ${{ steps.filter.outputs.agno }}
70+
CREW_AI: ${{ steps.filter.outputs.crew_ai }}
71+
LANGGRAPH: ${{ steps.filter.outputs.langgraph }}
72+
LLAMA_INDEX: ${{ steps.filter.outputs.llama_index }}
73+
MASTRA: ${{ steps.filter.outputs.mastra }}
74+
MIDDLEWARE_STARTER: ${{ steps.filter.outputs.middleware_starter }}
75+
PYDANTIC_AI: ${{ steps.filter.outputs.pydantic_ai }}
76+
SERVER_STARTER: ${{ steps.filter.outputs.server_starter }}
77+
SERVER_STARTER_ALL: ${{ steps.filter.outputs.server_starter_all }}
78+
VERCEL_AI_SDK: ${{ steps.filter.outputs.vercel_ai_sdk }}
79+
run: |
80+
python3 - << 'PY'
81+
import os, json
82+
83+
all_entries = [
84+
{"suite": "agno", "test_path": "tests/agnoTests", "services": ["dojo","agno"], "wait_on": "http://localhost:9999,tcp:localhost:8002"},
85+
{"suite": "crew-ai", "test_path": "tests/crewAITests", "services": ["dojo","crew-ai"], "wait_on": "http://localhost:9999,tcp:localhost:8003"},
86+
{"suite": "langgraph", "test_path": "tests/langgraphTests", "services": ["dojo","langgraph-platform-python","langgraph-platform-typescript"], "wait_on": "http://localhost:9999,tcp:localhost:8005,tcp:localhost:8006"},
87+
{"suite": "langgraph-fastapi", "test_path": "tests/langgraphFastAPITests", "services": ["dojo","langgraph-fastapi"], "wait_on": "http://localhost:9999,tcp:localhost:8004"},
88+
{"suite": "llama-index", "test_path": "tests/llamaIndexTests", "services": ["dojo","llama-index"], "wait_on": "http://localhost:9999,tcp:localhost:8007"},
89+
{"suite": "mastra", "test_path": "tests/mastraTests", "services": ["dojo","mastra"], "wait_on": "http://localhost:9999,tcp:localhost:8008"},
90+
{"suite": "mastra-agent-local", "test_path": "tests/mastraAgentLocalTests", "services": ["dojo"], "wait_on": "http://localhost:9999"},
91+
{"suite": "middleware-starter", "test_path": "tests/middlewareStarterTests", "services": ["dojo"], "wait_on": "http://localhost:9999"},
92+
{"suite": "pydantic-ai", "test_path": "tests/pydanticAITests", "services": ["dojo","pydantic-ai"], "wait_on": "http://localhost:9999,tcp:localhost:8009"},
93+
{"suite": "server-starter", "test_path": "tests/serverStarterTests", "services": ["dojo","server-starter"], "wait_on": "http://localhost:9999,tcp:localhost:8000"},
94+
{"suite": "server-starter-all", "test_path": "tests/serverStarterAllFeaturesTests", "services": ["dojo","server-starter-all"], "wait_on": "http://localhost:9999,tcp:localhost:8001"},
95+
{"suite": "vercel-ai-sdk", "test_path": "tests/vercelAISdkTests", "services": ["dojo"], "wait_on": "http://localhost:9999"},
96+
]
97+
98+
entry_by_suite = {e["suite"]: e for e in all_entries}
99+
core_changed = (
100+
(os.environ.get('CORE_TS') == 'true') or
101+
(os.environ.get('CORE_PY') == 'true') or
102+
(os.environ.get('E2E_TESTS') == 'true') or
103+
(os.environ.get('WORKFLOW_SELF') == 'true')
104+
)
105+
106+
include = []
107+
if core_changed:
108+
include = all_entries
109+
else:
110+
if os.environ.get('AGNO') == 'true':
111+
include.append(entry_by_suite['agno'])
112+
if os.environ.get('CREW_AI') == 'true':
113+
include.append(entry_by_suite['crew-ai'])
114+
if os.environ.get('LANGGRAPH') == 'true':
115+
include.append(entry_by_suite['langgraph'])
116+
include.append(entry_by_suite['langgraph-fastapi'])
117+
if os.environ.get('LLAMA_INDEX') == 'true':
118+
include.append(entry_by_suite['llama-index'])
119+
if os.environ.get('MASTRA') == 'true':
120+
include.append(entry_by_suite['mastra'])
121+
include.append(entry_by_suite['mastra-agent-local'])
122+
if os.environ.get('MIDDLEWARE_STARTER') == 'true':
123+
include.append(entry_by_suite['middleware-starter'])
124+
if os.environ.get('PYDANTIC_AI') == 'true':
125+
include.append(entry_by_suite['pydantic-ai'])
126+
if os.environ.get('SERVER_STARTER') == 'true':
127+
include.append(entry_by_suite['server-starter'])
128+
if os.environ.get('SERVER_STARTER_ALL') == 'true':
129+
include.append(entry_by_suite['server-starter-all'])
130+
if os.environ.get('VERCEL_AI_SDK') == 'true':
131+
include.append(entry_by_suite['vercel-ai-sdk'])
132+
133+
matrix = {"include": include}
134+
with open(os.environ['GITHUB_OUTPUT'], 'a') as fh:
135+
fh.write(f"matrix={json.dumps(matrix)}\n")
136+
fh.write(f"should_run={'true' if include else 'false'}\n")
137+
PY
10138
e2e:
11-
name: E2E Tests
12-
runs-on: depot-ubuntu-latest-8
139+
needs: changes
140+
if: ${{ needs.changes.outputs.should_run == 'true' }}
141+
name: ${{ matrix.suite }}
142+
runs-on: depot-ubuntu-24.04
143+
strategy:
144+
fail-fast: false
145+
matrix: ${{ fromJSON(needs.changes.outputs.matrix) }}
13146

14147
steps:
15148
- name: Checkout code
@@ -25,6 +158,33 @@ jobs:
25158
with:
26159
version: 10.13.1
27160

161+
# Now that pnpm is available, cache its store to speed installs
162+
- name: Resolve pnpm store path
163+
id: pnpm-store
164+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
165+
166+
- name: Cache pnpm store
167+
uses: actions/cache@v4
168+
with:
169+
path: ${{ env.STORE_PATH }}
170+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
171+
restore-keys: |
172+
${{ runner.os }}-pnpm-store-
173+
174+
# Cache Python tool caches and virtualenvs; restore only to avoid long saves
175+
- name: Cache Python dependencies (restore-only)
176+
id: cache-python
177+
uses: actions/cache/restore@v4
178+
with:
179+
path: |
180+
~/.cache/pip
181+
~/.cache/pypoetry
182+
~/.cache/uv
183+
**/.venv
184+
key: ${{ runner.os }}-pydeps-${{ hashFiles('**/poetry.lock', '**/pyproject.toml') }}
185+
restore-keys: |
186+
${{ runner.os }}-pydeps-
187+
28188
- name: Install Poetry
29189
uses: snok/install-poetry@v1
30190
with:
@@ -35,21 +195,14 @@ jobs:
35195
- name: Install uv
36196
uses: astral-sh/setup-uv@v6
37197

38-
- name: Setup pnpm cache
39-
uses: actions/cache@v4
40-
with:
41-
path: ~/.local/share/pnpm/store
42-
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
43-
restore-keys: |
44-
${{ runner.os }}-pnpm-store-
45-
46198
- name: Install dependencies
47199
working-directory: typescript-sdk
48200
run: pnpm install --frozen-lockfile
49201

50202
- name: Prepare dojo for e2e
51203
working-directory: typescript-sdk/apps/dojo
52-
run: node ./scripts/prep-dojo-everything.js -e2e
204+
if: ${{ join(matrix.services, ',') != '' }}
205+
run: node ./scripts/prep-dojo-everything.js --only ${{ join(matrix.services, ',') }}
53206

54207
- name: Install e2e dependencies
55208
working-directory: typescript-sdk/apps/dojo/e2e
@@ -61,6 +214,7 @@ jobs:
61214
env:
62215
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
63216
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
217+
if: ${{ contains(join(matrix.services, ','), 'langgraph-fastapi') || contains(join(matrix.services, ','), 'langgraph-platform-python') || contains(join(matrix.services, ','), 'langgraph-platform-typescript') }}
64218
run: |
65219
echo "OPENAI_API_KEY=${OPENAI_API_KEY}" > examples/python/.env
66220
echo "LANGSMITH_API_KEY=${LANGSMITH_API_KEY}" >> examples/python/.env
@@ -74,33 +228,28 @@ jobs:
74228
env:
75229
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
76230
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
231+
if: ${{ join(matrix.services, ',') != '' && contains(join(matrix.services, ','), 'dojo') }}
77232
with:
78233
run: |
79-
node ../scripts/run-dojo-everything.js
234+
node ../scripts/run-dojo-everything.js --only ${{ join(matrix.services, ',') }}
80235
working-directory: typescript-sdk/apps/dojo/e2e
81-
wait-on: |
82-
http://localhost:9999
83-
tcp:localhost:8000
84-
tcp:localhost:8001
85-
tcp:localhost:8002
86-
tcp:localhost:8003
87-
tcp:localhost:8004
88-
tcp:localhost:8005
89-
tcp:localhost:8006
90-
tcp:localhost:8007
91-
tcp:localhost:8008
92-
tcp:localhost:8009
93-
94-
- name: Run tests
236+
wait-on: ${{ matrix.wait_on }}
237+
wait-for: 300000
238+
239+
- name: Run tests – ${{ matrix.suite }}
95240
working-directory: typescript-sdk/apps/dojo/e2e
96241
env:
97242
BASE_URL: http://localhost:9999
98-
run: pnpm test
243+
PLAYWRIGHT_SUITE: ${{ matrix.suite }}
244+
run: |
245+
pnpm test -- ${{ matrix.test_path }}
99246
100-
- name: Upload traces
247+
- name: Upload traces – ${{ matrix.suite }}
101248
if: always() # Uploads artifacts even if tests fail
102249
uses: actions/upload-artifact@v4
103250
with:
104-
name: playwright-traces
105-
path: typescript-sdk/apps/dojo/e2e/test-results/
251+
name: ${{ matrix.suite }}-playwright-traces
252+
path: |
253+
typescript-sdk/apps/dojo/e2e/test-results/${{ matrix.suite }}/**/*
254+
typescript-sdk/apps/dojo/e2e/playwright-report/**/*
106255
retention-days: 7

typescript-sdk/apps/dojo/e2e/playwright.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function getBaseUrl(): string {
4646
export default defineConfig({
4747
timeout: process.env.CI ? 300_000 : 120_000, // 5min in CI, 2min locally for AI tests
4848
testDir: "./tests",
49-
retries: process.env.CI ? 1 : 0, // More retries for flaky AI tests in CI, 0 for local
49+
retries: process.env.CI ? 3 : 0, // More retries for flaky AI tests in CI, 0 for local
5050
// Make this sequential for now to avoid race conditions
5151
workers: process.env.CI ? 1 : undefined,
5252
fullyParallel: process.env.CI ? false : true,
@@ -66,7 +66,7 @@ export default defineConfig({
6666
baseURL: getBaseUrl(),
6767
},
6868
expect: {
69-
timeout: 90_000, // 1.5 minutes for AI-generated content to appear
69+
timeout: 120_000, // 2 minutes for AI-generated content to appear
7070
},
7171
// Test isolation between each test
7272
projects: [

0 commit comments

Comments
 (0)