@skyramp/mcp 0.4.4-rc.2 → 0.5.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/entitlements/entitlementGate.d.ts +8 -0
- package/build/entitlements/entitlementGate.js +57 -0
- package/build/entitlements/runCodes.d.ts +18 -0
- package/build/entitlements/runCodes.js +15 -0
- package/build/index.js +4 -4
- package/build/playwright/essentialTools.d.ts +1 -0
- package/build/playwright/essentialTools.js +43 -0
- package/build/playwright/registerPlaywrightTools.js +1 -43
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +46 -177
- package/build/prompts/test-maintenance/driftAnalysisShared.js +1 -1
- package/build/tool-phases.d.ts +14 -8
- package/build/tool-phases.js +83 -43
- package/build/tools/workspace/initScanWorkspaceTool.d.ts +11 -1
- package/build/tools/workspace/initScanWorkspaceTool.js +100 -9
- package/build/tools/workspace/initializeWorkspaceTool.js +40 -4
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +51 -121
- package/build/workspace/workspace.js +34 -47
- package/build/workspace/workspaceVerifier.d.ts +22 -0
- package/build/workspace/workspaceVerifier.js +65 -0
- package/package.json +2 -2
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/skills/setup-testbot-target-sut/SKILL.md +54 -0
- package/plugin/skills/setup-testbot-target-sut/reference/ci-workflow.md +26 -0
- package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +14 -0
- package/plugin/skills/setup-testbot-target-sut/reference/troubleshooting.md +20 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +0 -178
- package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +0 -2
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +0 -311
- package/build/prompts/sut-setup/shared.d.ts +0 -62
- package/build/prompts/sut-setup/shared.js +0 -196
- package/build/resources/sutSetupResource.d.ts +0 -2
- package/build/resources/sutSetupResource.js +0 -63
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import { buildContextBlock, buildInputsBody, buildCommonSutErrorsSection, buildLocalValidationSection, buildSeedDataDiscoverySection, buildSutDefinitionBlock, buildWorkspaceInitSection, } from "../shared.js";
|
|
2
|
-
import { getPersonaPrefix } from "../../personas.js";
|
|
3
|
-
import { PromptPlan } from "../../test-recommendation/promptPlan.js";
|
|
4
|
-
// ── Step body builders ────────────────────────────────────────────────────────
|
|
5
|
-
/**
|
|
6
|
-
* Step labels for the cross-references the bodies below make. Read from the
|
|
7
|
-
* plan's own declaration, not written out again: every one of these five
|
|
8
|
-
* references was off by one after `INIT_WORKSPACE` was added at the front.
|
|
9
|
-
* `_composePlan` is declared after the bodies, so this resolves on call.
|
|
10
|
-
*/
|
|
11
|
-
const STEP = (key) => {
|
|
12
|
-
const label = _composePlan.labels[key];
|
|
13
|
-
// Without this a renamed key renders "Step undefined" and nothing goes red.
|
|
14
|
-
if (label === undefined) {
|
|
15
|
-
throw new Error(`dockerComposePrompt: no step named "${key}"`);
|
|
16
|
-
}
|
|
17
|
-
return label;
|
|
18
|
-
};
|
|
19
|
-
function buildScanComposeBody(args) {
|
|
20
|
-
const composeFile = args.sutSourceDockerComposeFile?.trim();
|
|
21
|
-
if (composeFile) {
|
|
22
|
-
return `A specific compose file was provided: \`${composeFile}\`. Use it directly as the compose file — no scanning or generating. (Strategy in Step ${STEP("DECIDE")} will be REUSE with this file.)`;
|
|
23
|
-
}
|
|
24
|
-
return `Search the repository for existing docker-compose files:
|
|
25
|
-
- \`docker-compose.yml\`, \`docker-compose.yaml\`, \`compose.yml\`, \`compose.yaml\` at root
|
|
26
|
-
- \`docker-compose.*.yml\` variants (e.g., \`docker-compose.dev.yml\`, \`docker-compose.e2e.yml\`)
|
|
27
|
-
- Compose files in subdirectories (\`deploy/\`, \`.docker/\`, etc.)
|
|
28
|
-
|
|
29
|
-
For each compose file found, determine if it is:
|
|
30
|
-
- **Application compose** — builds or runs the actual backend API / frontend UI (has \`build:\` directives pointing to repo source, or services that are the app's entry points). USE THIS.
|
|
31
|
-
- **Infrastructure-only compose** — contains only databases, Redis, message queues, mail servers, object storage. NOT sufficient for SUT setup alone.`;
|
|
32
|
-
}
|
|
33
|
-
function buildDecideStrategyBody(args) {
|
|
34
|
-
const composeFile = args.sutSourceDockerComposeFile?.trim();
|
|
35
|
-
if (composeFile) {
|
|
36
|
-
return `A specific compose file was provided in Step ${STEP("SCAN")} (\`${composeFile}\`). Strategy is REUSE with this file. Confirm your decision:
|
|
37
|
-
|
|
38
|
-
\`\`\`
|
|
39
|
-
Decision: REUSE
|
|
40
|
-
Compose file: ${composeFile}
|
|
41
|
-
Reason: provided by the user
|
|
42
|
-
\`\`\``;
|
|
43
|
-
}
|
|
44
|
-
return `Choose between reuse and generate based on what Step ${STEP("SCAN")} found:
|
|
45
|
-
- If an application docker-compose file was found → strategy is REUSE.
|
|
46
|
-
- If only infrastructure-only compose files exist, or no compose files found → strategy is GENERATE.
|
|
47
|
-
|
|
48
|
-
Output your decision:
|
|
49
|
-
\`\`\`
|
|
50
|
-
Decision: REUSE | GENERATE
|
|
51
|
-
Compose file: <path to file if reusing, or "none" if generating>
|
|
52
|
-
Reason: <brief explanation>
|
|
53
|
-
\`\`\``;
|
|
54
|
-
}
|
|
55
|
-
function buildPrAccurateSutSection() {
|
|
56
|
-
return `The SUT must run code from the PR branch, not a pre-built upstream image. Determine how to build the application from the PR source:
|
|
57
|
-
|
|
58
|
-
1. If the repo uses Docker images for deployment (e.g., \`SERVER_IMAGE\` env var in compose), then build a local image from the PR code and use it. Do NOT use commit-SHA-tagged images from upstream registries (those only exist for the upstream repo's CI, not for forks or testbot PRs).
|
|
59
|
-
2. If the repo builds from source (Go binary, Node app, Python package), then include the build step in the workflow before starting services.
|
|
60
|
-
3. Use a stable base/build image (e.g., the repo's official build container or a standard runtime image).
|
|
61
|
-
4. The built image or binary stays local to the runner — no registry push needed.`;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Build the reuse instructions body (no section header — the caller supplies it).
|
|
65
|
-
* When `composeFile` is provided the concrete path is interpolated into the
|
|
66
|
-
* `docker compose -f <path>` commands so the agent wires the given file directly;
|
|
67
|
-
* otherwise generic `<path>` placeholders are kept for the discovery flow where
|
|
68
|
-
* the agent picks the file itself.
|
|
69
|
-
*/
|
|
70
|
-
function getReuseDockerComposeInstructions(composeFile) {
|
|
71
|
-
const file = composeFile?.trim();
|
|
72
|
-
const ref = file ?? "<path>";
|
|
73
|
-
const intro = file
|
|
74
|
-
? `Use the provided application docker-compose file \`${file}\` directly. Wire it as \`targetSetupCommand\`.`
|
|
75
|
-
: `You found an existing application docker-compose file. Wire it directly as \`targetSetupCommand\`.`;
|
|
76
|
-
return `${intro}
|
|
77
|
-
|
|
78
|
-
1. Verify the compose file starts the application (not just infrastructure).
|
|
79
|
-
2. Determine the correct command:
|
|
80
|
-
- If the compose uses \`build:\` directives: \`docker compose -f ${ref} up -d --build\`
|
|
81
|
-
- If it uses pre-built images: \`docker compose -f ${ref} up -d\`
|
|
82
|
-
- If it has builder services (one-shot build steps): run builders first, then start runtime services.
|
|
83
|
-
3. Determine the health check command from the compose file's \`healthcheck:\` definitions or from known health endpoints.
|
|
84
|
-
4. Determine the teardown command: \`docker compose -f ${ref} down -v\`
|
|
85
|
-
5. If the compose file defines admin credentials in environment variables, create \`.skyramp/sut/get-auth-token.sh\` using those credentials.
|
|
86
|
-
|
|
87
|
-
These resolved commands are wired onto the \`skyramp/testbot\` action step using the input semantics from Step ${STEP("INPUTS")} above.`;
|
|
88
|
-
}
|
|
89
|
-
/** Generate instructions used when no reusable application compose file exists. */
|
|
90
|
-
const GENERATE_DOCKER_COMPOSE_INSTRUCTIONS = `No usable application docker-compose was found. Generate one from scratch.
|
|
91
|
-
|
|
92
|
-
Steps:
|
|
93
|
-
1. Inspect the repository to determine how to build and run the application:
|
|
94
|
-
- Does it have a Dockerfile? → Use \`build: { context: . }\`
|
|
95
|
-
- No Dockerfile but has package.json? → Use \`node:20-alpine\` base image + volume mount + npm install + start
|
|
96
|
-
- No Dockerfile but has requirements.txt/pyproject.toml? → Use \`python:3.12-slim\` base image + pip install + start
|
|
97
|
-
- No Dockerfile but has pom.xml/build.gradle? → Use appropriate JDK image + build + run
|
|
98
|
-
|
|
99
|
-
2. Determine database requirements:
|
|
100
|
-
- If app references PostgreSQL → add postgres service with healthcheck
|
|
101
|
-
- If app references MongoDB → add mongo service with replica set
|
|
102
|
-
- If app references Redis → add redis service
|
|
103
|
-
- Prefer SQLite when the app supports it (simpler, no extra service)
|
|
104
|
-
|
|
105
|
-
3. Generate \`.skyramp/sut/docker-compose.testbot.yml\` with:
|
|
106
|
-
- The compose file must be runnable with: \`docker compose -f .skyramp/sut/docker-compose.testbot.yml --project-directory . up -d --build\`
|
|
107
|
-
- The \`--project-directory .\` means build context is the repository root (NOT .skyramp/sut/)
|
|
108
|
-
- Healthchecks for ALL services (testbot relies on \`--wait\`)
|
|
109
|
-
- Simple testbot credentials in environment (admin@testbot.com / testbot)
|
|
110
|
-
- Never paste cryptographic material into the \`environment:\` section of the compose file. This rule covers RSA and EC private keys, PEM blobs, JWK JSON, OAuth client secrets, and any other signing keypair. In practice, refuse anything that looks like a PEM block (text starting with \`-----BEGIN \` followed by \`PRIVATE KEY\`, \`RSA PRIVATE KEY\`, \`EC PRIVATE KEY\`, \`OPENSSH PRIVATE KEY\`, or \`ENCRYPTED PRIVATE KEY\` and the matching \`-----\`), a JWK in base64url-encoded JSON form (strings starting with \`eyJrdHkiOi\`, which decodes to \`{"kty":\`), or any base64 string longer than about 200 characters that looks like a key. The compose file is committed to the customer's repository, so anything written here lands in their PR diff and stays in git history — including the private half of any keypair. Two cases to handle:
|
|
111
|
-
1. **Symmetric secrets** — when the app accepts a plain string (\`JWT_SECRET\`, \`SESSION_SECRET\`, signing salts, HMAC keys), use a clearly-marked dummy literal so a code reviewer immediately recognizes it as fake. For example: \`JWT_SECRET: "testbot-dummy-do-not-use-in-production"\`.
|
|
112
|
-
2. **Asymmetric keys** — when the app requires a real keypair (RS256 JWT signing, gRPC TLS, signing keypairs), do not commit the keys. Instead, generate them inside the container at startup so they exist only for the container's lifetime. First look at the app's source or config to determine what format it expects: PEM, JWK, or raw base64. Then override the service's \`entrypoint\` with a short script that generates a keypair in that format, exports the key bodies into the env vars the app reads, and finally \`exec\`s the app's original command. Pick the tool that matches the format: \`openssl genpkey\` for PEM, \`python -m jwcrypto\` / \`node -e "require('jose')..."\` / \`step crypto jwk create\` for JWK, \`openssl rand -base64\` for a raw secret. Prefer a tool that already exists in the app's runtime image — for instance, a Django service usually has \`jwcrypto\` or \`josepy\` installed already — rather than installing one at startup. Testbot fetches its bearer token via \`get-auth-token.sh\`, which calls the app's login endpoint; the app signs the token with its in-container private key and returns it as an opaque string. The test client never reads the private key directly, so per-container ephemeral keys are enough — no GitHub repo secrets, no out-of-band key sharing.
|
|
113
|
-
- Generous \`start_period\` (30-120s) for CI cold builds
|
|
114
|
-
- For monorepos: may need a builder service that runs first
|
|
115
|
-
|
|
116
|
-
4. Generate \`.skyramp/sut/get-auth-token.sh\` if the application requires authentication:
|
|
117
|
-
- Script must output ONLY the token to stdout (debug output to stderr)
|
|
118
|
-
- Poll health endpoint before attempting auth (300s timeout)
|
|
119
|
-
- Credentials must match what's in the docker-compose environment variables
|
|
120
|
-
- Use \`set -euo pipefail\` and \`jq\` for JSON parsing
|
|
121
|
-
|
|
122
|
-
<example label="simple-single-service">
|
|
123
|
-
\`\`\`yaml
|
|
124
|
-
# .skyramp/sut/docker-compose.testbot.yml
|
|
125
|
-
services:
|
|
126
|
-
app:
|
|
127
|
-
build:
|
|
128
|
-
context: .
|
|
129
|
-
dockerfile: Dockerfile
|
|
130
|
-
ports:
|
|
131
|
-
- "8055:8055"
|
|
132
|
-
environment:
|
|
133
|
-
SECRET: testbot-secret
|
|
134
|
-
ADMIN_EMAIL: admin@testbot.com
|
|
135
|
-
ADMIN_PASSWORD: testbot
|
|
136
|
-
DB_CLIENT: sqlite3
|
|
137
|
-
healthcheck:
|
|
138
|
-
test: ["CMD", "curl", "-sf", "http://localhost:8055/health"]
|
|
139
|
-
interval: 5s
|
|
140
|
-
timeout: 5s
|
|
141
|
-
retries: 24
|
|
142
|
-
start_period: 15s
|
|
143
|
-
\`\`\`
|
|
144
|
-
</example>
|
|
145
|
-
|
|
146
|
-
<example label="service-with-postgres">
|
|
147
|
-
\`\`\`yaml
|
|
148
|
-
services:
|
|
149
|
-
postgres:
|
|
150
|
-
image: postgres:16-alpine
|
|
151
|
-
environment:
|
|
152
|
-
POSTGRES_DB: app
|
|
153
|
-
POSTGRES_USER: app
|
|
154
|
-
POSTGRES_PASSWORD: testbot
|
|
155
|
-
healthcheck:
|
|
156
|
-
test: ["CMD-SHELL", "pg_isready -U app"]
|
|
157
|
-
interval: 5s
|
|
158
|
-
timeout: 5s
|
|
159
|
-
retries: 10
|
|
160
|
-
|
|
161
|
-
app:
|
|
162
|
-
build:
|
|
163
|
-
context: .
|
|
164
|
-
ports:
|
|
165
|
-
- "3000:3000"
|
|
166
|
-
environment:
|
|
167
|
-
DATABASE_URL: postgres://app:testbot@postgres/app
|
|
168
|
-
depends_on:
|
|
169
|
-
postgres:
|
|
170
|
-
condition: service_healthy
|
|
171
|
-
healthcheck:
|
|
172
|
-
test: ["CMD", "curl", "-sf", "http://localhost:3000/health"]
|
|
173
|
-
interval: 10s
|
|
174
|
-
timeout: 5s
|
|
175
|
-
retries: 30
|
|
176
|
-
start_period: 30s
|
|
177
|
-
\`\`\`
|
|
178
|
-
</example>
|
|
179
|
-
|
|
180
|
-
<example label="monorepo-with-builder">
|
|
181
|
-
\`\`\`yaml
|
|
182
|
-
services:
|
|
183
|
-
builder:
|
|
184
|
-
image: node:20-alpine
|
|
185
|
-
working_dir: /app
|
|
186
|
-
volumes:
|
|
187
|
-
- .:/app
|
|
188
|
-
command: sh -c "corepack enable && pnpm install --frozen-lockfile && pnpm build"
|
|
189
|
-
|
|
190
|
-
postgres:
|
|
191
|
-
image: postgres:16-alpine
|
|
192
|
-
environment:
|
|
193
|
-
POSTGRES_DB: app
|
|
194
|
-
POSTGRES_USER: app
|
|
195
|
-
POSTGRES_PASSWORD: testbot
|
|
196
|
-
healthcheck:
|
|
197
|
-
test: ["CMD-SHELL", "pg_isready -U app"]
|
|
198
|
-
interval: 5s
|
|
199
|
-
timeout: 5s
|
|
200
|
-
retries: 10
|
|
201
|
-
|
|
202
|
-
backend:
|
|
203
|
-
build: { context: . }
|
|
204
|
-
depends_on:
|
|
205
|
-
builder:
|
|
206
|
-
condition: service_completed_successfully
|
|
207
|
-
postgres:
|
|
208
|
-
condition: service_healthy
|
|
209
|
-
ports:
|
|
210
|
-
- "9000:9000"
|
|
211
|
-
environment:
|
|
212
|
-
DATABASE_URL: postgres://app:testbot@postgres/app
|
|
213
|
-
JWT_SECRET: testbot-jwt-secret
|
|
214
|
-
healthcheck:
|
|
215
|
-
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9000/health"]
|
|
216
|
-
interval: 10s
|
|
217
|
-
timeout: 5s
|
|
218
|
-
retries: 30
|
|
219
|
-
start_period: 90s
|
|
220
|
-
|
|
221
|
-
frontend:
|
|
222
|
-
image: node:20-alpine
|
|
223
|
-
working_dir: /app/apps/web
|
|
224
|
-
volumes:
|
|
225
|
-
- .:/app
|
|
226
|
-
depends_on:
|
|
227
|
-
backend:
|
|
228
|
-
condition: service_healthy
|
|
229
|
-
ports:
|
|
230
|
-
- "3000:3000"
|
|
231
|
-
environment:
|
|
232
|
-
NEXT_PUBLIC_API_URL: http://localhost:9000
|
|
233
|
-
command: sh -c "npm run dev -- --hostname 0.0.0.0 --port 3000"
|
|
234
|
-
healthcheck:
|
|
235
|
-
test: ["CMD-SHELL", "bash -c '</dev/tcp/localhost/3000' || exit 1"]
|
|
236
|
-
interval: 10s
|
|
237
|
-
timeout: 5s
|
|
238
|
-
retries: 30
|
|
239
|
-
start_period: 120s
|
|
240
|
-
\`\`\`
|
|
241
|
-
</example>
|
|
242
|
-
|
|
243
|
-
<example label="source-mount-no-dockerfile">
|
|
244
|
-
\`\`\`yaml
|
|
245
|
-
services:
|
|
246
|
-
app:
|
|
247
|
-
image: python:3.12-slim
|
|
248
|
-
working_dir: /app
|
|
249
|
-
volumes:
|
|
250
|
-
- .:/app
|
|
251
|
-
command: sh -c "pip install -e . && uvicorn main:app --host 0.0.0.0 --port 8000"
|
|
252
|
-
ports:
|
|
253
|
-
- "8000:8000"
|
|
254
|
-
healthcheck:
|
|
255
|
-
test: ["CMD", "curl", "-sf", "http://localhost:8000/health"]
|
|
256
|
-
interval: 10s
|
|
257
|
-
timeout: 5s
|
|
258
|
-
retries: 30
|
|
259
|
-
start_period: 60s
|
|
260
|
-
\`\`\`
|
|
261
|
-
</example>
|
|
262
|
-
`;
|
|
263
|
-
function buildApplyStrategyBody(args) {
|
|
264
|
-
const composeFile = args.sutSourceDockerComposeFile?.trim();
|
|
265
|
-
if (composeFile) {
|
|
266
|
-
return `#### Reuse the provided Docker Compose file
|
|
267
|
-
${getReuseDockerComposeInstructions(composeFile)}`;
|
|
268
|
-
}
|
|
269
|
-
return `Apply the strategy selected in Step ${STEP("DECIDE")}:
|
|
270
|
-
|
|
271
|
-
#### If REUSE selected — Reuse existing Docker Compose
|
|
272
|
-
${getReuseDockerComposeInstructions()}
|
|
273
|
-
|
|
274
|
-
#### If GENERATE selected — Generate new Docker Compose
|
|
275
|
-
${GENERATE_DOCKER_COMPOSE_INSTRUCTIONS}`;
|
|
276
|
-
}
|
|
277
|
-
// ── PromptPlan declaration ────────────────────────────────────────────────────
|
|
278
|
-
const _composePlan = new PromptPlan()
|
|
279
|
-
.addPhase("compose", "", { headerLevel: "hidden", stepFormat: "hash" })
|
|
280
|
-
.step("INIT_WORKSPACE", "Initialise the Skyramp workspace file", () => buildWorkspaceInitSection())
|
|
281
|
-
.step("SCAN", "Understand the System Under Test in this repository", buildScanComposeBody)
|
|
282
|
-
.step("DECIDE", "Decide SUT setup strategy", buildDecideStrategyBody)
|
|
283
|
-
.step("INPUTS", "Plan the Testbot action inputs", () => buildInputsBody())
|
|
284
|
-
.step("SEED_DATA", "Discover database seed-data conventions", () => buildSeedDataDiscoverySection())
|
|
285
|
-
.step("BUILD", "Edit the Testbot workflow and generate SUT files", () => "")
|
|
286
|
-
.subStep("PR_ACCURATE", "Build PR-accurate SUT", buildPrAccurateSutSection)
|
|
287
|
-
.subStep("APPLY", "Apply the chosen strategy", buildApplyStrategyBody)
|
|
288
|
-
.step("VERIFY", "Verify", () => "")
|
|
289
|
-
.subStep("COMMON_ERRORS", "Common SUT errors", () => buildCommonSutErrorsSection())
|
|
290
|
-
.subStep("LOCAL_VALIDATION", "Local validation", (args) => buildLocalValidationSection(args))
|
|
291
|
-
.done();
|
|
292
|
-
export function getDockerComposePrompt(args) {
|
|
293
|
-
const composeFile = args.sutSourceDockerComposeFile?.trim();
|
|
294
|
-
const taskLine = composeFile
|
|
295
|
-
? `Your task is to find a working System Under Test (SUT) setup using the given Docker Compose file \`${composeFile}\`. You will reuse this file, validate that services start correctly, fix any errors, and wire the working commands into the testbot workflow.`
|
|
296
|
-
: `Your task is to find a working System Under Test (SUT) setup using Docker Compose that builds and deploys the services in this repository. You will either reuse an existing docker-compose file or generate a new one, validate that services start correctly, fix any errors, and wire the working commands into the testbot workflow.`;
|
|
297
|
-
const thinkingStep = composeFile
|
|
298
|
-
? `1. Output a <thinking> block to explain how the provided compose file \`${composeFile}\` starts the application and what commands are needed to bring it up.`
|
|
299
|
-
: `1. Output a <thinking> block to explain whether you found an existing compose file, whether it is application or infrastructure-only, and your decision to reuse or generate.`;
|
|
300
|
-
return `${getPersonaPrefix()}${taskLine}
|
|
301
|
-
|
|
302
|
-
${buildSutDefinitionBlock()}The testbot installer has already created a basic \`.github/workflows/skyramp-testbot.yml\` in this PR with no SUT setup commands. Manage the SUT lifecycle exclusively through inputs on the existing \`skyramp/testbot\` action step — do NOT add separate GHA setup/teardown steps and do NOT set \`skipTargetSetup\`.
|
|
303
|
-
|
|
304
|
-
Before generating any files, you MUST:
|
|
305
|
-
${thinkingStep}
|
|
306
|
-
2. Then proceed with the instructions below.
|
|
307
|
-
${buildContextBlock(args)}
|
|
308
|
-
|
|
309
|
-
${_composePlan.render(args)}
|
|
310
|
-
`;
|
|
311
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path of the Testbot workflow file relative to the repo root. Created by the
|
|
3
|
-
* Testbot installer and edited in-place by every SUT setup mode.
|
|
4
|
-
*/
|
|
5
|
-
export declare const TESTBOT_WORKFLOW_PATH = ".github/workflows/skyramp-testbot.yml";
|
|
6
|
-
export declare enum SutSetupMode {
|
|
7
|
-
None = "none",
|
|
8
|
-
AdaptWorkflow = "adapt_workflow",
|
|
9
|
-
DockerCompose = "docker_compose"
|
|
10
|
-
}
|
|
11
|
-
export interface SutPromptArgs {
|
|
12
|
-
repositoryPath: string;
|
|
13
|
-
/** `owner/repo` of the checkout at repositoryPath. The canary calls
|
|
14
|
-
* skyramp_execute_test, which names its repository on every call. */
|
|
15
|
-
repository: string;
|
|
16
|
-
sutSetupMode: string;
|
|
17
|
-
sutSourceWorkflowFile: string;
|
|
18
|
-
sutSourceDockerComposeFile: string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Glossary block rendered right after the first mention of "SUT" in each
|
|
22
|
-
* setup prompt. Shared across modes so the LLM gets a single explicit
|
|
23
|
-
* definition rather than inferring it from context.
|
|
24
|
-
*/
|
|
25
|
-
export declare function buildSutDefinitionBlock(): string;
|
|
26
|
-
export declare function buildContextBlock(args: SutPromptArgs): string;
|
|
27
|
-
/**
|
|
28
|
-
* Seed-data discovery block — rendered as a SUT-bootstrap step so the agent
|
|
29
|
-
* looks for existing customer seed conventions BEFORE inventing new ones.
|
|
30
|
-
* A fresh database makes list / search / filter / pagination endpoint tests
|
|
31
|
-
* shallow (only HTTP-status assertable, never response content), which
|
|
32
|
-
* collapses the data-diversity dimension of the eval. Most repos already
|
|
33
|
-
* have a seed mechanism — wire that in instead of generating one.
|
|
34
|
-
*
|
|
35
|
-
* Rendered as a top-level PromptPlan step between INPUTS and the
|
|
36
|
-
* ADAPT/BUILD step in every SUT setup mode.
|
|
37
|
-
*/
|
|
38
|
-
export declare function buildSeedDataDiscoverySection(): string;
|
|
39
|
-
/**
|
|
40
|
-
* Workspace file initialisation block — rendered as the first instruction
|
|
41
|
-
* in every SUT setup mode, before any local validation, workflow editing,
|
|
42
|
-
* or SUT-file generation. Seeds `.skyramp/workspace.yml` with the service
|
|
43
|
-
* inventory so testbot's SUT autocommit picks it up alongside the
|
|
44
|
-
* generated `.skyramp/sut/*` files.
|
|
45
|
-
*/
|
|
46
|
-
export declare function buildWorkspaceInitSection(): string;
|
|
47
|
-
export declare function buildLocalValidationSection(args: SutPromptArgs): string;
|
|
48
|
-
/**
|
|
49
|
-
* Returns the canonical "add to the Testbot workflow" block: every input that
|
|
50
|
-
* controls the SUT lifecycle, auth, and workflow-level setup, followed by the
|
|
51
|
-
* shared What to do / What not to do guidance (including the sutSetupMode
|
|
52
|
-
* bootstrap guard). Shared by every SUT setup mode (adapt_workflow and
|
|
53
|
-
* docker_compose) so the input semantics and rules never drift between prompts.
|
|
54
|
-
*/
|
|
55
|
-
export declare function buildAddToTestbotWorkflowSection(): string;
|
|
56
|
-
/**
|
|
57
|
-
* `buildAddToTestbotWorkflowSection()` output with its leading
|
|
58
|
-
* `#### Testbot action inputs ...` header stripped — for use as a PromptPlan
|
|
59
|
-
* step body where the PromptPlan step header replaces the inline `####`.
|
|
60
|
-
*/
|
|
61
|
-
export declare function buildInputsBody(): string;
|
|
62
|
-
export declare function buildCommonSutErrorsSection(): string;
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { RUN_TEST_COMMAND_RULES } from "../../skills/runTestSkill.js";
|
|
2
|
-
import { runnableLanguagesText } from "../../types/TestTypes.js";
|
|
3
|
-
/**
|
|
4
|
-
* Path of the Testbot workflow file relative to the repo root. Created by the
|
|
5
|
-
* Testbot installer and edited in-place by every SUT setup mode.
|
|
6
|
-
*/
|
|
7
|
-
export const TESTBOT_WORKFLOW_PATH = ".github/workflows/skyramp-testbot.yml";
|
|
8
|
-
export var SutSetupMode;
|
|
9
|
-
(function (SutSetupMode) {
|
|
10
|
-
SutSetupMode["None"] = "none";
|
|
11
|
-
SutSetupMode["AdaptWorkflow"] = "adapt_workflow";
|
|
12
|
-
SutSetupMode["DockerCompose"] = "docker_compose";
|
|
13
|
-
})(SutSetupMode || (SutSetupMode = {}));
|
|
14
|
-
/**
|
|
15
|
-
* Glossary block rendered right after the first mention of "SUT" in each
|
|
16
|
-
* setup prompt. Shared across modes so the LLM gets a single explicit
|
|
17
|
-
* definition rather than inferring it from context.
|
|
18
|
-
*/
|
|
19
|
-
export function buildSutDefinitionBlock() {
|
|
20
|
-
return `**System Under Test (SUT)** — the application services this workflow brings up so Testbot can exercise them end-to-end. The SUT is what runs the PR's source code: backend services (REST/gRPC APIs), their supporting infrastructure (databases, queues, caches), and any frontend UI under test. The SUT does NOT include the test runner itself or Testbot. Bringing up the SUT means building from PR source, starting all required services, seeding any data tests rely on, and exposing a health check that proves the system is ready for traffic.
|
|
21
|
-
|
|
22
|
-
`;
|
|
23
|
-
}
|
|
24
|
-
export function buildContextBlock(args) {
|
|
25
|
-
const lines = [];
|
|
26
|
-
lines.push(`<context>`);
|
|
27
|
-
lines.push(`Repository path: ${args.repositoryPath}`);
|
|
28
|
-
lines.push(`Setup mode: ${args.sutSetupMode}`);
|
|
29
|
-
if (args.sutSourceWorkflowFile)
|
|
30
|
-
lines.push(`Source workflow file: ${args.sutSourceWorkflowFile}`);
|
|
31
|
-
if (args.sutSourceDockerComposeFile)
|
|
32
|
-
lines.push(`Source docker-compose file: ${args.sutSourceDockerComposeFile}`);
|
|
33
|
-
lines.push(`</context>`);
|
|
34
|
-
return lines.join("\n");
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Seed-data discovery block — rendered as a SUT-bootstrap step so the agent
|
|
38
|
-
* looks for existing customer seed conventions BEFORE inventing new ones.
|
|
39
|
-
* A fresh database makes list / search / filter / pagination endpoint tests
|
|
40
|
-
* shallow (only HTTP-status assertable, never response content), which
|
|
41
|
-
* collapses the data-diversity dimension of the eval. Most repos already
|
|
42
|
-
* have a seed mechanism — wire that in instead of generating one.
|
|
43
|
-
*
|
|
44
|
-
* Rendered as a top-level PromptPlan step between INPUTS and the
|
|
45
|
-
* ADAPT/BUILD step in every SUT setup mode.
|
|
46
|
-
*/
|
|
47
|
-
export function buildSeedDataDiscoverySection() {
|
|
48
|
-
return `**First, decide if this step applies.** Skip it entirely (in your summary state \`SEED_DATA: N/A — <reason>\`, naming the specific bullet below that applied — e.g. "no stateful service", "no list endpoints in scope", "already seeded by init container") and move on when ANY of the following is true:
|
|
49
|
-
- The SUT has no database / stateful service (it's a pure stateless API, a static asset server, a webhook receiver, etc.).
|
|
50
|
-
- The compose stack contains no database container (no \`postgres\`, \`mysql\`, \`mariadb\`, \`mongodb\`, \`redis\` as the primary store, \`sqlite\`, etc.) and no application-managed persistent store.
|
|
51
|
-
- The PR diff and the existing test surface focus exclusively on auth, single-resource CRUD (POST/GET-one/PUT/DELETE on \`/foo/{id}\`), or compute endpoints — no list / search / filter / pagination / aggregate endpoints will be exercised.
|
|
52
|
-
- You can confirm from the SCAN step's findings that representative records already exist (e.g. seeded by an init container, by a migration, or by the auth/setup flow you already wired).
|
|
53
|
-
|
|
54
|
-
Only proceed with the rest of this step when the SUT has a database AND the tests will exercise list/search/filter/pagination endpoints. Otherwise an empty database is acceptable and inventing seed data adds noise.
|
|
55
|
-
|
|
56
|
-
When this step DOES apply: database services usually start empty in CI, so tests against list / search / filter / pagination endpoints will then only see \`[]\` and can assert nothing beyond HTTP status — a low data-diversity outcome. Look for an existing seeding mechanism in the repo and wire it into the SUT bootstrap.
|
|
57
|
-
|
|
58
|
-
**Discover existing project conventions first** — most repos already have one of:
|
|
59
|
-
1. A named script: \`scripts/seed*\`, \`db/seeds/*\`, \`prisma/seed.{ts,js}\`, \`apps/api/seed/*\`, \`tests/fixtures/seed*\`. Search: \`find . -iname '*seed*' -not -path '*/node_modules/*' -not -path '*/.git/*'\`.
|
|
60
|
-
2. A package.json / Makefile target: \`npm run seed\`, \`npm run db:seed\`, \`pnpm seed\`, \`yarn seed\`, \`make seed\`, \`make fixtures\`. Check \`package.json\` \`scripts\` and the project's \`Makefile\`.
|
|
61
|
-
3. A migration with sample data: Django \`loaddata\`, Rails \`db:seed\`, Knex \`seed:run\`, Prisma \`db seed\`, Alembic data migrations.
|
|
62
|
-
4. A docker-compose init service: existing compose files sometimes have a \`*-seed\`, \`*-fixtures\`, or \`init-*\` service that runs once on bring-up.
|
|
63
|
-
|
|
64
|
-
**Wire it in:**
|
|
65
|
-
- **Compose init service** — add it (or its dependency) to the testbot compose so \`targetSetupCommand\` brings it up alongside the rest. Use \`depends_on\` with \`condition: service_completed_successfully\` on dependent services so they only start after seeding finishes.
|
|
66
|
-
- **Script needing an admin/API token** (most common) — chain it INTO \`authTokenCommand\` AFTER the token is created and BEFORE the script prints the token to stdout. Route seed stdout/stderr to stderr so the captured token isn't corrupted. Make the seed call best-effort (\`|| echo "seed failed" >&2\`) so a flaky seed doesn't block the run — the token is still returned and PRs whose tests don't need seed data keep working.
|
|
67
|
-
- **Migration with sample data** — ensure the migration runs as part of \`targetSetupCommand\` (some ORMs auto-run on container start, others need an explicit \`migrate\` / \`db:seed\` step before tests can hit the API).
|
|
68
|
-
|
|
69
|
-
**If NO existing convention is found**, generate a minimal seed step (≥3 entities per primary resource the tests will query, e.g., ≥3 repos / ≥3 issues / ≥3 users for a forge; ≥3 products / ≥3 orders for a shop). Call this out explicitly in your summary so the user knows a richer fixture may be needed for production-quality tests.
|
|
70
|
-
|
|
71
|
-
**Never** hard-code production data, real customer credentials, or PII in seeds. Seed identifiers should be obviously test-only (\`acme-co\`, \`alpha-svc\`, \`testbot-admin\`).`;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Workspace file initialisation block — rendered as the first instruction
|
|
75
|
-
* in every SUT setup mode, before any local validation, workflow editing,
|
|
76
|
-
* or SUT-file generation. Seeds `.skyramp/workspace.yml` with the service
|
|
77
|
-
* inventory so testbot's SUT autocommit picks it up alongside the
|
|
78
|
-
* generated `.skyramp/sut/*` files.
|
|
79
|
-
*/
|
|
80
|
-
export function buildWorkspaceInitSection() {
|
|
81
|
-
return `Before generating any SUT setup files, check whether \`.skyramp/workspace.yml\` exists at the root of the repository being bootstrapped (the path shown next to "Repository path:" in the context block above — the repo whose PR triggered Testbot; in multi-repo runs this is the primary repo, not the sibling repos checked out as related repos). If it doesn't:
|
|
82
|
-
1. Call \`skyramp_init_scan\` with \`workspacePath\` set to that repository root → follow the returned instructions to discover all services.
|
|
83
|
-
2. Call \`skyramp_init_workspace\` with the same \`workspacePath\`, \`services\`, and the \`scanToken\` from step 1.
|
|
84
|
-
|
|
85
|
-
Later steps in this SUT bootstrap run write \`.skyramp/sut/*\` files alongside the workspace file. If you need to update per-service \`runtimeDetails.serverStartCommand\` to match the new SUT files, call \`skyramp_init_workspace\` again (see its tool description for the edit-mode / \`force\` contract) rather than editing \`.skyramp/workspace.yml\` directly.`;
|
|
86
|
-
}
|
|
87
|
-
export function buildLocalValidationSection(args) {
|
|
88
|
-
return `Before reporting success, exercise the adapted workflow locally. Testbot's external fix loop only retries when SUT lifecycle commands are set on the Testbot action. When the SUT is brought up by surrounding GHA steps (skipTargetSetup: 'true'), the fix loop is skipped — so the local check below is the only safety net before the workflow is committed.
|
|
89
|
-
Run commands one at a time in your shell — individual execution pinpoints failures far faster than running the whole workflow at once. For any setup, build, or lifecycle command, a non-zero exit is a fix-needed signal: adjust the workflow, re-run the failing command, and do not proceed until it passes. (The suite canary in step 4 is the one exception — judge it on whether the test executes, not on its pass/fail result.)
|
|
90
|
-
1. Validate build scripts and helper programs first — run these before anything else so build and auth failures surface cheaply:
|
|
91
|
-
a. Every helper script the workflow calls (e.g., \`./scripts/*.sh\`, \`make <target>\`, \`./gradlew <task>\`) — run each and confirm exit 0.
|
|
92
|
-
b. \`.skyramp/sut/get-auth-token.sh\` if present — run it **twice**, and confirm the SECOND run also prints a non-empty token: it is invoked more than once per run, so a script that only works against a fresh SUT passes a single check here and fails later. Then send that token to an endpoint that REQUIRES authentication. A public one (a health, version, or site endpoint) answers 200 for an empty token too, so it cannot tell a working token from a missing one.
|
|
93
|
-
c. Validate any standalone Dockerfile referenced by GHA \`steps:\` (not by a compose service) with \`docker build\`, unless a pre-step in step 2 already builds it. Do NOT also run \`docker build\` on Dockerfiles that belong to a compose service — \`docker compose build\` (next step) already builds them in one BuildKit graph; doubling up wastes ~5-10 GB of cache per service.
|
|
94
|
-
d. Every docker-compose file the workflow references — run \`docker compose -f <path> config\` to validate the YAML, then \`docker compose -f <path> build\`.
|
|
95
|
-
e. **Disk hygiene between heavy builds.** Between any two build commands (Dockerfile, compose, helper scripts that build), if the runner is under ~5 GB free or you see \`no space left on device\`, prune docker BuildKit cache and dangling images before the next build (keep the running stack intact). Skipping this on heavy SUTs exhausts the standard runner disk mid-validation.
|
|
96
|
-
2. Every GHA step you added before the Testbot action must have run and exited 0 by this point. Run any that has not, in workflow order. If one needs a \${{ secrets.* }} value your shell cannot resolve, note it in <thinking> and skip that one rather than fabricating a value.
|
|
97
|
-
3. Validate the SUT lifecycle comes up — choose the branch that matches the chosen pattern:
|
|
98
|
-
a. If lifecycle commands are set on the Testbot action (skipTargetSetup is unset): run \`targetSetupCommand\` (confirm exit 0), then poll \`targetReadyCheckCommand\` until it exits 0 within \`targetReadyCheckTimeout\` seconds.
|
|
99
|
-
b. If GHA steps wrap the Testbot action (skipTargetSetup: 'true'): the SETUP steps you ran in step 2 already brought the SUT up, so just poll \`targetReadyCheckCommand\` until it exits 0.
|
|
100
|
-
4. Canary the repository's own suite(s) — do this only when a suite was recorded (a \`testSuites\` entry under a service's \`runtimeDetails\` in \`workspace.yml\`); skip it when none was recorded. With the SUT still running from step 3 and before teardown, pick one real test FILE per recorded suite. For each file, call **\`skyramp_execute_test\`** with \`commandOverride\`: the suite's recorded \`testRunCommand\` limited to that file, plus \`cwd\`, \`testFile\`, \`language\`, \`testType\`, and \`repository\`: "${args.repository}". Shape the command this way: ${RUN_TEST_COMMAND_RULES}. Pass no \`stateFile\`. Do NOT canary by running the suite's command in your shell. \`skyramp_execute_test\` takes ${runnableLanguagesText()} only: a suite in any other language (Go, RSpec, Jasmine) cannot be canaried this way — record \`canaryStatus: unproven\` with \`canaryDetail\` naming the language.
|
|
101
|
-
- Cover **every** suite in \`runtimeDetails.testSuites\`, not a subset you choose: do not rank them or trim by cost. A suite that spawns its OWN app instances (per-worker containers via its own orchestration) belongs in the set like any other — it depends entirely on your provisioning, so its weight is a reason to run it, never a reason to defer it. HTTP readiness only shows the app answers; one real test per suite is what proves each suite's environment is present — its browser, the built e2e image, dev/test dependencies, and base-URL wiring.
|
|
102
|
-
- Read the verdict per file, and the output with it. Some runners exit 0 when they find no test (mocha; junit with \`-DfailIfNoTests=false\`), so \`Pass\` on its own does not prove a test ran: the output must show at least one test from that file executing. If it does not, the suite is not proven — treat it as you would an \`Error\`. \`Fail\` does prove the command works, because only a test that ran can fail. \`Error\` means that it does not — the output says why, and that text belongs in the suite's \`canaryDetail\`.
|
|
103
|
-
- A \`Fail\` verdict is fine — the environment is runnable, so proceed. An \`Error\` verdict is the fix-needed signal — the exit criteria below say what you must fix and when you are allowed to stop trying. Never edit, skip, or delete the repo's tests to force a pass; that is out of scope for setup.
|
|
104
|
-
5. Tear down — run \`targetTeardownCommand\` (branch a), or each TEARDOWN step's body in order (branch b), so the next iteration starts clean; confirm exit 0.
|
|
105
|
-
Only proceed to the success report once pre-steps → setup → health check → canary (every suite in the mandatory set) → teardown all pass, and every recorded suite carries a \`canaryStatus\`. \`proven\` requires that you watched a test execute. \`unproven\` is NOT a way to close out a suite you could have fixed — it is permitted only when the blocker is something only CI can grant (a secret, a registry credential, a hosted dependency), and then \`canaryDetail\` must name that grant. Every other cause is yours to fix before you finish, and those are the common ones: a runner binary the suite needs that is installed in the repo but absent from \`PATH\`, an image nobody built, infra the bring-up never started, a wrong path in \`testRunCommand\`. Fix the artifact — \`setup.sh\`, the workflow pre-steps, \`workspace.yml\` — and canary that suite again. Only after two genuine fix attempts have failed on the same cause may you record \`unproven\`, with \`canaryDetail\` naming both the cause and what you tried. **Keep every suite recorded either way** — an unproven suite is still maintained from source, so removing the entry is the only move that loses that coverage outright.`;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Returns the canonical "add to the Testbot workflow" block: every input that
|
|
109
|
-
* controls the SUT lifecycle, auth, and workflow-level setup, followed by the
|
|
110
|
-
* shared What to do / What not to do guidance (including the sutSetupMode
|
|
111
|
-
* bootstrap guard). Shared by every SUT setup mode (adapt_workflow and
|
|
112
|
-
* docker_compose) so the input semantics and rules never drift between prompts.
|
|
113
|
-
*/
|
|
114
|
-
export function buildAddToTestbotWorkflowSection() {
|
|
115
|
-
return `#### Testbot action inputs - Add target lifecycle commands and auth to Testbot Workflow
|
|
116
|
-
For the full Testbot action input reference (defaults, edge cases, advanced options beyond the ones called out below), see the Testbot README: https://github.com/letsramp/testbot#inputs
|
|
117
|
-
|
|
118
|
-
Target lifecycle inputs (control how Testbot starts, validates, and stops the SUT):
|
|
119
|
-
1. \`targetSetupCommand\` — shell command that builds and starts all SUT services. Testbot runs this before tests.
|
|
120
|
-
a. Default when unset: \`docker compose up -d\` (starts existing images, does NOT build).
|
|
121
|
-
b. For PR-source builds use \`docker compose up -d --build\` or the equivalent for helm/script-based SUTs.
|
|
122
|
-
c. With a dedicated compose file (reused or generated), point at it explicitly: \`docker compose -f .skyramp/sut/docker-compose.testbot.yml --project-directory . up -d --build\`.
|
|
123
|
-
d. Override whenever the source workflow's start sequence differs from the default.
|
|
124
|
-
e. Launch-only when a preceding workflow step already built the app image (e.g. an expensive image built once in a GHA pre-step): use \`docker compose up -d\` (NO \`--build\`) against the pre-built \`image:\` tag, so the multi-minute build stays off the agent-timed fix-loop path.
|
|
125
|
-
2. \`targetReadyCheckCommand\` — polling command Testbot runs repeatedly after setup until it exits 0 (service ready) or the timeout fires.
|
|
126
|
-
a. **CRITICAL: must cover EVERY service the tests will hit, not just the backend.** Chain checks with \`&&\` so the command exits 0 only when ALL services respond. A single-service check on a multi-service SUT is the leading cause of "element not visible" / "heading not visible" UI test failures — Playwright lands on a partially-rendered page because only the API was up when the gate passed. Inventory of services to include:
|
|
127
|
-
- Every API service in workspace.yml with an \`api.baseUrl\` → curl an unauthenticated endpoint (health / version / status).
|
|
128
|
-
- Every UI service with a UI \`baseUrl\` (or a frontend port in compose) → curl \`/\` or a known asset like \`/assets/index.js\` (more reliable than \`/\` for SPAs that hydrate lazily).
|
|
129
|
-
- Skip auth-required endpoints — they 401 before the SUT is testable and mask the readiness signal.
|
|
130
|
-
Examples:
|
|
131
|
-
- API only: \`curl -sf http://localhost:8080/api/health\`
|
|
132
|
-
- API + UI: \`curl -sf http://localhost:8080/api/health && curl -sf http://localhost:3000/\`
|
|
133
|
-
Required in every pattern (explicit \`targetSetupCommand\`, the \`docker compose up -d\` default, AND \`skipTargetSetup: 'true'\`). Without a real readiness signal (neither this nor a workspace service \`baseUrl\`), testbot's fail-early gate throws \`Unable to verify service readiness\` and the action exits non-zero. It is also what gates a backgrounded \`targetSetupCommand\` — without it Testbot can't tell when the service is actually listening.
|
|
134
|
-
b. Set \`targetReadyCheckDiagnosticsCommand\` to a command that explains why the SUT isn't healthy — output is surfaced in the failure PR comment when the ready check times out. For docker-compose SUTs, scope it to the project's compose file (container state + recent service logs). For non-docker SUTs, a tail of the server log.
|
|
135
|
-
3. \`targetReadyCheckTimeout\` — seconds to wait for \`targetReadyCheckCommand\` to succeed.
|
|
136
|
-
a. Default: \`1800\` (30 min) — sized for cold \`docker compose --build\` runs on multi-service stacks. The loop exits on the first success, so fast services pay no extra cost. Override only if you want to fail faster on stuck setups (e.g. \`'300'\` for a small single-service SUT).
|
|
137
|
-
4. \`targetTeardownCommand\` — shell command that stops and cleans up the SUT after tests (e.g., \`docker compose down -v\`, or \`docker compose -f .skyramp/sut/docker-compose.testbot.yml --project-directory . down -v\` for a dedicated compose file).
|
|
138
|
-
a. Always set this so each run starts from a clean state.
|
|
139
|
-
b. Omit only when teardown is handled by surrounding GHA steps with \`if: always()\`.
|
|
140
|
-
5. \`skipTargetSetup\` — when \`'true'\`, Testbot skips running \`targetSetupCommand\` entirely.
|
|
141
|
-
a. Set this only when GHA steps surrounding the Testbot action already bring the SUT up.
|
|
142
|
-
b. Leave it unset (or \`'false'\`) when Testbot should run the full lifecycle via the inputs above.
|
|
143
|
-
Auth inputs (used by Testbot to authenticate against the running SUT during test recording and execution):
|
|
144
|
-
6. \`authTokenCommand\` — shell command whose stdout is the auth credential for REST/gRPC API testing (e.g., a Bearer token, API key, or session cookie). Testbot captures the output and injects it as the \`Authorization\` header.
|
|
145
|
-
a. Set this when the SUT requires authentication for API calls.
|
|
146
|
-
b. If the source workflow exports a token via a step output or script, wire that same script here (e.g., \`bash .skyramp/sut/get-auth-token.sh\`).
|
|
147
|
-
c. If the workflow seeds a test user during setup, create \`.skyramp/sut/get-auth-token.sh\` that logs in with those credentials and prints the token.
|
|
148
|
-
d. **This command runs more than once per run, so it must be idempotent.** Anything it creates against a fresh SUT — an admin user, an integration, an API key, seed data — already exists the next time it runs, so look each one up first and create only what is missing. Make the lookup ask for whatever the API needs in order to return the credential: many APIs omit nested keys unless the request includes them, and then the second run finds nothing, tries to create a duplicate, is refused, and exits empty-handed.
|
|
149
|
-
e. Omit when the SUT APIs are unauthenticated.
|
|
150
|
-
7. \`uiCredentials\` — \`username:password\` pair typed into the browser login form during UI test recording (format: \`myuser:mypassword\`).
|
|
151
|
-
a. Set for any frontend service that requires browser-based login.
|
|
152
|
-
b. Use \`\${{ secrets.SKYRAMP_UI_CREDENTIALS }}\` if the secret exists, otherwise use credentials seeded during SUT setup.
|
|
153
|
-
Workflow / job level settings (set on the workflow or job, not as \`skyramp/testbot\` action inputs):
|
|
154
|
-
8. Secrets / environment variables — pass every secret/env var the setup needs into the workflow via an \`env:\` block or \`secrets: inherit\`, and add any dependency-resolution steps/jobs the SUT requires.
|
|
155
|
-
9. \`runs-on\` runner label — leave whatever value the Testbot installer wrote and the source workflow used. Don't override it. Picking a runner SKU is an account-level decision that depends on provisioning the agent can't see from inside the repo; a wrong choice queues the job forever with no runner ever picking it up. If the SUT genuinely doesn't fit on the chosen runner, the disk-pressure mitigation in "Common failure modes" #9 handles it; an unprovisioned-runner stall does not.
|
|
156
|
-
|
|
157
|
-
#### What to do
|
|
158
|
-
Edit \`${TESTBOT_WORKFLOW_PATH}\` in place (already created by the Testbot installer) — do NOT recreate it. Add only what the SUT needs onto the existing \`skyramp/testbot\` action step, alongside the inputs the installer already added:
|
|
159
|
-
1. The 4 target lifecycle inputs — \`targetSetupCommand\`, \`targetReadyCheckCommand\`, \`targetReadyCheckTimeout\`, \`targetTeardownCommand\`.
|
|
160
|
-
2. The 2 auth inputs when the SUT needs them — \`authTokenCommand\` and/or \`uiCredentials\`.
|
|
161
|
-
3. Any secrets / environment variables and dependency-resolution steps/jobs the setup needs (input 8).
|
|
162
|
-
4. Leave the existing \`runs-on\` value untouched (input 9).
|
|
163
|
-
5. Use a daemon/detached form for \`targetSetupCommand\` — \`docker compose up -d --build\`, \`nohup npm start &\`, \`./server &\`, \`pnpm --prefix ./apps/<app> start &\`. The command should launch the service and exit; \`targetReadyCheckCommand\` is what gates readiness. Testbot backgrounds foreground-blocking commands (\`npm start\`, \`python app.py\`, \`./server\`) automatically after a 5-minute timeout as a safety net, so they will eventually work — but daemon mode is faster (no 5-minute wait) and surfaces startup crashes clearly via the command's exit code. If no daemon form is available and the repo has no \`docker-compose.yml\`, wrap the foreground command in compose (a \`Dockerfile\` + \`docker-compose.yml\` running \`npm start\`) so it can be launched with \`docker compose up -d --build\`. Only fall back to a GHA pre-step with \`&\` (e.g. \`run: npm start &\`) if Docker is not feasible.
|
|
164
|
-
6. Stack-specific testability flags — some frameworks gate test-harness affordances behind a container env var. Common cases: Flutter web needs \`IS_TESTING=true\` so \`ensureSemantics()\` exposes the widget tree to Playwright (without it every selector finds nothing even though the app is up); some React/Angular builds need \`NODE_ENV\` / framework-specific build flags for \`testID\` propagation. Set these in the compose \`environment:\` block on the affected service (or as a \`Dockerfile ENV\`) — NOT as a shell var in \`targetSetupCommand\`. The var must be in the container's env at boot, not just the shell that runs the setup command. If selectors find nothing but the app is up, check for a missing flag like this first.
|
|
165
|
-
|
|
166
|
-
#### What not to do
|
|
167
|
-
1. IMPORTANT — do NOT remove or change the existing \`sutSetupMode\` input (nor \`sutSourceWorkflowFile\` / \`sutSourceDockerComposeFile\`). Leave \`sutSetupMode\` exactly as provided: Testbot flips it to \`none\` automatically after validation succeeds, and the committed \`sutSetupMode\` value is the signal that tells the next CI run to validate this adapted workflow rather than run tests. Deleting it or setting it to \`none\` breaks the bootstrap/validation cycle.
|
|
168
|
-
2. Do not point the SUT at a prebuilt upstream image (e.g. \`image: org/app:latest\` or a fixed commit SHA from \`main\`) for any application service under test. The image will lag the PR and Testbot will validate stale code. In case of multi-mode containers (\`Dockerfile.bdd\`, \`Dockerfile.e2e\`, \`Dockerfile.integration\`, \`Dockerfile.test\`), create the necessary file in \`.skyramp/sut/\` with a PR-sourced build (e.g. \`.skyramp/sut/Dockerfile.bdd\` referenced from the testbot compose's \`build:\`). Do not edit existing Dockerfiles or compose files in the repo. If the multi-mode container's bootstrap step depends on a seeded user, ensure SEED_DATA creates it before this container tries to log in.
|
|
169
|
-
3. Do not point the SUT at a remote staging or production environment for application services under test. Staging code drifts from PR source and turns Testbot's results into noise. External infra is acceptable only for sidecar dependencies the PR does not change (e.g. a managed test database).
|
|
170
|
-
4. Do not use a foreground-blocking command as \`targetSetupCommand\` (e.g. \`npm start\`, \`pnpm --prefix ./apps/<app> start\`, \`python app.py\`, \`./server\`) — see input 5 in "What to do" above for daemon forms and the compose-wrap fallback.
|
|
171
|
-
5. Do not leave server processes running between fix-loop retries. If \`targetSetupCommand\` starts a direct process (not \`docker compose\`), always set \`targetTeardownCommand\` to kill it (e.g. \`pkill -f "node dist/index.js"\` or \`docker compose down\`). Without teardown, retried setup attempts will fail with \`EADDRINUSE\` because the previous process still holds the port.
|
|
172
|
-
`;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* `buildAddToTestbotWorkflowSection()` output with its leading
|
|
176
|
-
* `#### Testbot action inputs ...` header stripped — for use as a PromptPlan
|
|
177
|
-
* step body where the PromptPlan step header replaces the inline `####`.
|
|
178
|
-
*/
|
|
179
|
-
export function buildInputsBody() {
|
|
180
|
-
const text = buildAddToTestbotWorkflowSection();
|
|
181
|
-
const firstNewline = text.indexOf("\n");
|
|
182
|
-
return text.slice(firstNewline + 1);
|
|
183
|
-
}
|
|
184
|
-
export function buildCommonSutErrorsSection() {
|
|
185
|
-
return `Before finishing, verify the adapted Testbot workflow setup against common errors seen during SUT setup:
|
|
186
|
-
1. port_conflict — another container is already using the port. Pick a free host port or stop the conflicting container.
|
|
187
|
-
2. image_not_found / build_failed — the referenced image does not exist for this SHA, or the build step has the wrong context/Dockerfile path. Build from PR source, do not rely on upstream commit-SHA tags.
|
|
188
|
-
3. healthcheck_timeout — the service is slow to come up. Use \`targetReadyCheckTimeout >= 120\` and ensure the health endpoint actually exists.
|
|
189
|
-
4. connection_refused — the service is not listening on the expected host/port (often binding to 127.0.0.1 inside the container; bind to 0.0.0.0).
|
|
190
|
-
5. dependency_error — required service (DB, cache, queue) is missing from compose or not healthy before the app starts. Add \`depends_on\` with \`condition: service_healthy\`.
|
|
191
|
-
6. permission_denied — script is not executable (\`chmod +x\`) or volume mount has wrong ownership.
|
|
192
|
-
7. auth_endpoint_404 / auth_credentials_invalid — the auth script hits a wrong path, or the database has no seeded user. The auth script must create the user before login when starting from a fresh DB.
|
|
193
|
-
8. missing env vars / secrets — required env vars are unset on the runner. Either default them in the script or pass them through workflow \`env:\`.
|
|
194
|
-
9. no_space_left — the runner ran out of disk space (\`No space left on device\`). Prune the docker BuildKit cache and dangling images at the start of both \`targetSetupCommand\` and \`targetTeardownCommand\` so each fix-loop retry begins with a clean slate; without it, cache from the previous attempt accumulates across retries on heavy SUTs. Do not change \`runs-on\` — see input 9 above.
|
|
195
|
-
Do not add an in-prompt fix loop — Testbot's external validator will retry with concrete error context if any of these occur.`;
|
|
196
|
-
}
|