Performance Testing
Previewops can automatically run a k6 load test against your preview environment after every deploy, giving you latency percentiles, throughput, and error rates directly in the PR. You can also run tests on demand with a comment command.
Quick start
Zero config — just comment
/perf-previewops
With no performance configuration, Previewops detects affected endpoints in the PR and runs a 30-second load test with 10 VUs. Results appear as a PR comment with p50/p90/p95/p99 latency, requests per second (RPS), and error rate.
Enable auto-testing on every deploy
Add to your .previewops.yaml:
performance:
enabled: true
The test starts automatically after each successful deploy — no comment needed.
Plan limits
Performance testing requires Premium BYOC, Pro, or higher. It runs k6 load tests using operator compute resources, which is why it is not available on the Free plan.
| Feature | Premium BYOC | Pro | Enterprise | Custom |
|---|---|---|---|---|
| Max VUs | 10 | 50 | 100 | 150 |
| Max duration | 30 min | 30 min | 30 min | 30 min |
| Runs per month | 100 | Unlimited | Unlimited | Unlimited |
| AI-generated scenarios | ✅ ¹ | ✅ | ✅ | ✅ |
| Historical baselines | ✅ ² | ✅ ² | ✅ ² | ✅ ² |
¹ Premium BYOC AI features require the free Custom LLM add-on. Activate it in the dashboard and store your
LLM_PROVIDERandLLM_API_KEY;LLM_MODELis optional. Without the add-on, the load test still runs, but AI scenario generation and post-run insights are skipped. See add-ons.md.
² Historical baseline saving and comparison require the Advanced Perf add-on.
With Advanced Perf, all paid plans have a 60-minute maximum duration.
During the seven-day Premium BYOC trial, performance tests are capped at 5 VUs. The trial also has a shared allowance of 10 AI-powered QA and performance runs across the organisation. Paid Premium BYOC includes 100 performance runs per billing period with no overage; after the allowance is exhausted, new performance runs are blocked until it resets.
When your config requests more VUs or time than your plan allows, the test runs with the plan cap applied and a notice is added to the PR comment. No silent clamping.
For example, a Premium BYOC test without Advanced Perf that requests 50 VUs for 45 minutes is reduced to 10 VUs for 30 minutes. The PR comment reports both the requested and applied values:
⚠️ Plan limits applied Your config requested 50 VUs · 45min but the Premium BYOC plan allows max of 10 VUs · 30min. The test is running with these limits.
Commands
Run a test
/perf-previewops
Runs with defaults (10 VUs, 30s, endpoints auto-detected from the PR diff).
Override parameters
/perf-previewops --vus=50 --duration=2m --target-tps=100
| Flag | Type | Description |
|---|---|---|
--vus=N |
int | Virtual users (1–150; capped by plan) |
--duration=<time> |
string | Test duration using s, m, min, or h, such as 30s, 2m, or 1h. The minimum is 10 seconds, and the value is capped by plan. |
--target-tps=N |
int | Apply a best-effort upper limit to aggregate requests per second. This can slow a test down but cannot make the configured VUs reach a higher rate. |
--set-baseline |
flag | Save this run as the repository baseline for future delta comparisons. Requires Advanced Perf. |
--provider |
string | Target the preview deployed to this provider. Useful when multiple active previews exist for the same PR. Aliases are supported: lightsail → aws-lightsail, ecs → aws-ecs, azure → azure-container-apps. If no matching preview is found, a clear error is posted and the test does not run. |
--env |
string | Target the preview with this environment name (set via --env when originally deployed). Lowercase alphanumeric and hyphens, max 20 characters. |
Save a baseline (Pro / Enterprise / Custom / Premium BYOC)
Requires the Advanced Perf add-on.
/perf-previewops --set-baseline
Future runs on this repository will show a "vs. Baseline" table with p95/p99 delta and error-rate change. Only one baseline is stored per repository — --set-baseline replaces any previous one.
Target a specific preview (when multiple are active)
If a PR has more than one active preview environment, use --provider or --env to select which one to test:
/perf-previewops --provider=render
/perf-previewops --env=staging
/perf-previewops --provider=fly --env=staging --vus=20 --duration=1m
If no preview matching the filter is found, Previewops posts an error comment with the filter that was used and suggests running /list-previewops to see which environments are currently active. The test does not start.
Configuration
Add a performance block to your .previewops.yaml:
performance:
enabled: false # Auto-run after every deploy (default: false)
vus: 10 # Virtual users (default: 10, max per plan)
duration: 30s # Test duration — s, m, or h (default: 30s)
rampUp: 5s # How long to ramp up to full VU count (default: 5s)
targetTps: 100 # Optional best-effort requests/second cap
apiBasePath: /api/v1 # Prepended to all detected/configured paths
slo: # Service Level Objectives (violations are warnings, not failures)
p90: 400 # ms — p90 latency threshold (optional)
p95: 500 # ms — p95 latency threshold
p99: 1000 # ms — p99 latency threshold
errorRate: 0.05 # fraction — max error rate (default: 0.05 = 5%)
scenarios: # Explicit endpoints to test (overrides auto-detection)
- path: /users
method: GET
- path: /orders
method: POST
body: '{"item": "test"}'
headers:
Content-Type: application/json
scriptPath: k6/load-test.js # Use a trusted custom k6 script from your repository
If slo.errorRate is omitted—or if the entire slo block is absent—the default maximum error rate is 0.05 (5%).
Load-test safety: Run performance tests only against isolated, non-production environments. Mutation methods such as
POST,PUT,PATCH, andDELETEare repeated during the test and may create data or trigger external integrations. Do not commit credentials, authorization tokens, customer data, or other secrets in scenario headers, request bodies, or custom scripts.
Using a custom k6 script
If you specify scriptPath, Previewops reads that file from the repository at the PR head and injects TARGET_URL as an environment variable so your script can use const BASE_URL = __ENV.TARGET_URL.
The custom script controls its own VUs, duration, ramp-up stages, requests, and throughput. The corresponding .previewops.yaml values and command flags do not override the script's k6 options, although plan limits are still evaluated before the run and configured SLOs are evaluated against the resulting aggregate metrics. If Previewops cannot load scriptPath, it falls back to a generated script.
Custom k6 scripts execute on Previewops-managed build infrastructure and can make outbound network requests. Only reference reviewed scripts from trusted pull requests. A hard-coded URL in a script is not restricted to TARGET_URL.
How endpoints are detected
When no explicit scenarios are configured in .previewops.yaml, Previewops automatically detects which endpoints changed in the PR:
File scan (all eligible plans) — Inspects up to the first 100 changed files for route definitions matching common patterns:
- Express / Fastify / Koa —
app.get('/path', ...),router.post('/path', ...) - Next.js App Router —
app/**/route.tsfile paths mapped to API routes. File-based detection defaults toGET; configure explicit scenarios when another method is required. - FastAPI / Flask —
@app.get('/path')decorator patterns - Generic —
// GET /api/pathcomment conventions
- Express / Fastify / Koa —
AI-generated scenarios (Pro / Enterprise / Custom — or Premium BYOC with the Custom LLM add-on) — When an LLM is configured, selected and truncated PR changes are sent to the model, which generates more context-aware scenarios. Previewops falls back to the file scan if the LLM is unavailable.
If no endpoints are detected, Previewops tests the root path / (or apiBasePath + / if configured).
Understanding the results
After the test completes, Previewops posts (or updates) the PR comment with:
🏎️ Performance test complete for PR #42
⚡ **p95** 180ms · **15.3 req/s** · **0.20%** errors · ✅ SLO PASS
### 📊 Results
| Metric | Value |
|---|---|
| p50 latency | 48ms |
| p90 latency | 120ms |
| p95 latency | 180ms |
| p99 latency | 350ms |
| Avg latency | 90ms |
| Throughput | 15.3 req/s |
| Error rate | 0.20% |
| Total requests | 918 |
**SLO:** ✅ SLO PASS
---
### 📈 vs. Baseline
| Metric | Delta |
|---|---|
| p95 | 🔴 +45ms (+33.3%) |
| p99 | 🟡 +15ms (+4.5%) |
| Error rate | +0.01pp |
---
### 🤖 AI Analysis
The p95 regression appears tied to the new middleware added in `src/middleware/auth.ts`…
---
<details>
<summary>🔧 Test Setup</summary>
- **VUs:** 10
- **Duration:** 30s
- **Model:** gpt-5.6
</details>
SLO violations
SLO violations are informational only — they never block a deploy or fail the PR. They are surfaced as warnings so your team can decide whether to merge.
Baseline comparison
The 🔴 🟡 🟢 traffic-light icons on the baseline delta indicate:
- 🟢 Improvement or no regression
- 🟡 Regression up to 10%
- 🔴 Regression greater than 10%
AI analysis (Pro / Enterprise / Custom — or Premium BYOC with the Custom LLM add-on)
Pro / Enterprise / Custom use the shared operator LLM key automatically — no setup required.
Premium BYOC requires the Custom LLM add-on: activate it in the dashboard and store your LLM_PROVIDER, LLM_API_KEY, and, optionally, LLM_MODEL credentials. Supported providers are OpenAI, Anthropic, and Gemini. Self-hosted operators configure the same variables directly in their environment.
Activating the add-on and storing a key are two separate steps. If the add-on is active but no key is stored yet, the load test still runs and the results comment says the AI analysis is not configured, with a link to finish the setup — rather than omitting the AI sections with no explanation.
- Summary — 2–4 sentence overview of performance health
- Potential bottlenecks — Possible endpoint issues with actionable suggestions
- Possible root cause — Single-sentence hypothesis, when identifiable
- Recommendation — Overall action item for the team
For scenario generation, the provider can receive up to 6,000 characters from patches in the first 100 changed files. Post-run analysis can include aggregate performance metrics, SLO violations, tested endpoints, baseline deltas, and up to 3,000 characters of patches from the first 50 changed files. These limits mean the provider does not necessarily receive every changed file or the complete diff.
Previewops does not intentionally add repository access tokens or configured credential values to LLM prompts. However, secrets or restricted data written directly into diffs, source files, scenario bodies, or headers may be included. Do not run AI features on content that violates your organisation's data-handling policy, and never commit credentials or secrets to a repository.
AI observations are advisory. The model receives aggregate k6 results rather than per-endpoint latency measurements, so endpoint bottlenecks and root causes are hypotheses that require validation.
FAQ
Does a perf test failure block the deploy?
No. Performance tests run asynchronously after the deploy succeeds. SLO violations are warnings only.
Can I run the test before deploying?
Not currently — the test requires a live preview URL to test against.
What k6 version is used?
Previewops currently uses k6 0.54.0 through a controlled Artifact Registry mirror.
How long does the test take end-to-end?
A 30-second test typically completes within 2 minutes including ramp-up, execution, and result processing.
Can I have different configs per branch or environment?
Not per-branch. The .previewops.yaml config applies to all PRs. Use command flags (--vus, --duration) to override per-run.