@postman/postman-plugin 0.5.1 → 0.6.0

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.
Files changed (34) hide show
  1. package/README.md +2 -2
  2. package/hooks/session-start-context.md +1 -1
  3. package/mcp.pi.json +2 -2
  4. package/package.json +1 -1
  5. package/skills/api-discovery/SKILL.md +7 -2
  6. package/skills/api-documentation/SKILL.md +2 -3
  7. package/skills/api-engineer/SKILL.md +13 -15
  8. package/skills/api-mocking/SKILL.md +4 -2
  9. package/skills/api-monitoring/SKILL.md +35 -25
  10. package/skills/api-simulation/SKILL.md +3 -1
  11. package/skills/api-testing/SKILL.md +3 -3
  12. package/skills/application-testing/SKILL.md +2 -2
  13. package/skills/ci-integration/SKILL.md +7 -5
  14. package/skills/cli-install/SKILL.md +29 -0
  15. package/skills/collection-schema-v3/SKILL.md +21 -11
  16. package/skills/collection-schema-v3/reference/environment.md +2 -0
  17. package/skills/collection-schema-v3/reference/globals.md +40 -0
  18. package/skills/collection-schema-v3/reference/http.md +149 -11
  19. package/skills/collection-schema-v3/reference/other_protocols.md +232 -86
  20. package/skills/datasets/SKILL.md +6 -5
  21. package/skills/datasets/reference/schema.md +51 -0
  22. package/skills/flows/SKILL.md +0 -1
  23. package/skills/performance-testing/SKILL.md +14 -1
  24. package/skills/performance-testing/references/gatling-import/README.md +141 -0
  25. package/skills/performance-testing/references/gatling-import/feeders.md +31 -0
  26. package/skills/performance-testing/references/gatling-import/injection.md +88 -0
  27. package/skills/performance-testing/references/gatling-import/mapping.md +91 -0
  28. package/skills/performance-testing/references/gatling-import/unsupported.md +31 -0
  29. package/skills/postman-mcp-server/SKILL.md +1 -1
  30. package/skills/variables/SKILL.md +2 -2
  31. package/skills/webhook-testing/SKILL.md +1 -1
  32. package/skills/workspaces/SKILL.md +53 -0
  33. package/skills/bootstrap/SKILL.md +0 -140
  34. package/skills/bootstrap/reference/cli_installation.md +0 -59
@@ -0,0 +1,88 @@
1
+ # Load model: injection, throttle, duration, assertions → `postman performance run`
2
+
3
+ ## What Postman supports (verify against the installed `postman performance run --help`; don't assume a version)
4
+ - **Closed model only.** `--vu-count N` virtual users each loop the collection until the run ends. There's no arrival rate and no target RPS.
5
+ - `--load-profile`: `fixed` (N the whole time), `ramp-up` (grows to N), `spike` (a burst to N), `peak` (rises to N, holds, falls). Each has a fixed shape; you can't set stage times from the CLI.
6
+ - `--duration` in whole minutes (≥ 1). `--pass-if` takes **one** condition: `less_than|greater_than|less_than_eq|greater_than_eq(avg|p90|p95|p99|error_rate|rps, value)`. `error_rate` is a percentage.
7
+ - Cloud runs need `--vu-count` ≥ 10. For local runs, any value ≥ 1 works.
8
+
9
+ ## Injection (`inj:*`)
10
+ **Closed steps: exact or near-exact.**
11
+ | Gatling | Flags | Status |
12
+ |---|---|---|
13
+ | `constantConcurrentUsers(n).during(d)` alone | `--vu-count n --load-profile fixed --duration ceil(d)` | mapped (approximated if d isn't whole minutes: say "rounded up") |
14
+ | `rampConcurrentUsers(a).to(b).during(d)`, then `constantConcurrentUsers(b).during(h)` | `--vu-count b --load-profile ramp-up --duration ceil(d+h)` | approximated: the ramp/hold ratio is fixed by the profile |
15
+ | `rampConcurrentUsers(a).to(b)` alone | `--load-profile ramp-up` | approximated |
16
+ | `incrementConcurrentUsers` (steps) | `ramp-up` to the final level | approximated: no stairs |
17
+
18
+ **Open steps: always approximated.** Gatling open users run the journey **once** and leave. Postman VUs loop. To keep the same load on the server, match the concurrency using Little's law:
19
+
20
+ > **VUs ≈ arrival rate (users/s) × journey time (s)**
21
+
22
+ - **Know whether the injector's number is a count or a rate — this is the most common mistake.** `constantUsersPerSec(r)` and `rampUsersPerSec(a).to(b)` are **rates** (users **per second**) — use them directly. `rampUsers(n).during(d)` and `atOnceUsers(n)` are a **total count** of users, not a rate: `rampUsers(50).during(60s)` is 50 users spread over 60 s = an arrival rate of `50/60 ≈ 0.83 users/s`, **not** 50/s. Convert a count to a rate (`n/d`) *before* Little's law. Skipping this step inflates `--vu-count` by the factor `d`.
23
+ - **Journey time** = the sum of pauses (use the mean for ranges, e.g. `pause(1,3)` → 2 s) + the expected response times. Use `0.3 s` per request if unknown, and say so — **but** if a path makes the latency evident (e.g. `/delay/1` ≈ 1 s, a Gatling request literally named "slow …"), use that value instead and note it. Pick one rule per conversion and state it, so two runs don't diverge.
24
+ - **Cloud VU floor.** If the computed `--vu-count` is below 10 and the user will run on **cloud**, keep the computed value but add a one-line note that cloud requires ≥ 10 (so they raise it deliberately, knowing it increases load). For **local** runs any value ≥ 1 is fine. Never silently inflate the count to clear the floor.
25
+ - **`pace(d)` is a floor, not an addend.** `pace` forces each iteration to take *at least* `d`; it does **not** add to the request/pause time, it subsumes it. So when a scenario has `pace(d)`, the journey time is `J = max(d, pauses + response times)`, **not** `pauses + response times + d`. Adding `pace` as a term overstates `J` and inflates `--vu-count`. Show the `max(...)` in MIGRATION.md.
26
+ - **A loop inside the per-user journey changes what `J` is.** If the open-model journey wraps a `during(t) { … }` / `forever { … }` loop (often with `pace`), each arriving user stays busy for the **whole loop duration**, so `J` is that duration, not one pass. Example: `constantUsersPerSec(20)` over a journey of `during(5.minutes){ pace(5s).exec(...) }` → `J = 300 s`, so `VUs ≈ 20 × 300 = 6000`, not `20 × (one 5 s iteration)`. This is usually a very large number — **do not silently pick the one-iteration reading**; compute both, put the arithmetic and the chosen reading in MIGRATION.md → Load model, and tell the user to confirm, because it swings `--vu-count` by orders of magnitude. A finite closed cap (`maxDuration`, cloud VU limits) may force you lower; say so.
27
+ - Round up to `--vu-count` ≥ 1. **Do not bump a sub-10 result up to 10 for cloud here** — the **Cloud VU floor** rule above governs that: keep the computed value and warn the user cloud needs ≥ 10, so load is never silently increased without their decision.
28
+
29
+ | Gatling | Flags |
30
+ |---|---|
31
+ | `constantUsersPerSec(r).during(d)` | `--vu-count ceil(r × J) --load-profile fixed --duration ceil(d)` |
32
+ | `rampUsersPerSec(a).to(b).during(d)` (then `constantUsersPerSec(b)…`) | `--vu-count ceil(b × J) --load-profile ramp-up`, with duration the sum of all steps |
33
+ | `atOnceUsers(n)` | a burst: `--load-profile spike --vu-count n` if it's the main (only sustained) step. **When it coexists with a ramp/constant step, the sustained step is the dominant shape** — use that profile and its Little's-law VU count; fold the burst in as the initial spike and note it. Only when the burst's `n` exceeds the sustained VU count does it set the peak (use the larger, and say so) |
34
+ | `rampUsers(n).during(d)` | **`n` is a total count, not a rate.** `n` users arrive evenly over `d`, so first convert to a rate `n/d` users/s, then `--vu-count ceil((n/d) × J)`, ramp-up. E.g. `rampUsers(50).during(60s)` → `50/60 ≈ 0.83/s`, **not** 50/s |
35
+ | `stressPeakUsers(n).during(d)` | `--load-profile peak --vu-count ceil(peak rate × J)` |
36
+ | `nothingFor(d)` | none. Add d to the explanation. Gap if it's the only step, otherwise approximated |
37
+ | `.randomized()` | none: gap (arrivals aren't randomised) |
38
+
39
+ When the chain has several steps, choose **one** profile for the dominant shape and put every step with its arithmetic in MIGRATION.md → Load model. Also write what the user should watch for: "Gatling measured N users arriving per second; Postman keeps V users busy. Compare throughput (rps) with your Gatling report rather than user counts."
40
+
41
+ Example (show your working like this):
42
+ ```
43
+ Source: rampUsersPerSec(1).to(10).during(60s), then constantUsersPerSec(10).during(120s)
44
+ Journey: 2 requests × 0.3 s (assumed) + pause(1,2) mean 1.5 s = 2.1 s
45
+ Peak VUs: 10 users/s × 2.1 s = 21 → --vu-count 21
46
+ Duration: 60 + 120 = 180 s = 3 min → --duration 3
47
+ Profile: ramp-up (one ramp then a hold; the ramp/hold split is fixed by the profile, not 1:2)
48
+ ```
49
+
50
+ Count-based injectors — convert the count to a rate FIRST (this is the step that most often goes wrong):
51
+ ```
52
+ Source: atOnceUsers(5), then rampUsers(50).during(60s)
53
+ Rate: rampUsers(50).during(60s) = 50 users over 60 s = 50/60 ≈ 0.83 users/s ← NOT 50/s
54
+ Journey: 3 requests × 0.3 s (assumed) + pause 0.5 s × 3 + /delay/1 ≈ 1 s = ~3.7 s
55
+ Sustained VUs: 0.83 users/s × 3.7 s ≈ 3.08 → 4
56
+ Burst: atOnceUsers(5) = 5 users at once; 5 > 4, so the burst sets the peak → --vu-count 5
57
+ Duration: 60 s = 1 min → --duration 1 ; Profile: ramp-up
58
+ ```
59
+ (Reading `rampUsers(50)` as 50 users/s here would give `50 × 3.7 ≈ 185` VUs — ~40× too high.)
60
+
61
+ ## Throttle (`throttle:*`) and max duration
62
+ - `throttle(reachRps(r).in(t), holdFor(h))`: there's no RPS cap. **Gap**, and say that Postman won't hold throughput at r. The user can lower `--vu-count` to get near it.
63
+ - `maxDuration(d)`: `--duration` is already a hard stop. Mapped to `run:--duration` if d ≥ the computed duration. Otherwise use d, and approximate.
64
+
65
+ ## Assertions (`assert:*`)
66
+ The CLI takes **at most one** `--pass-if`. Pick it by this **strict priority — this overrides the order the assertions appear in the source**:
67
+ 1. a global error-rate assertion (`failedRequests`/`successfulRequests` → `error_rate`)
68
+ 2. a global latency percentile (`p95`, then `p99`, then `p90`)
69
+ 3. a global `mean` (`avg`) or `requestsPerSec` (`rps`)
70
+
71
+ Walk the list top-down and take the **first category that a source assertion matches**, even if a different assertion came first in the source. Example: a simulation with `responseTime().percentile(95).lt(800)` **and** `failedRequests().percent().lt(1)` → the `--pass-if` is `less_than(error_rate, 1)` (category 1 wins), and the p95 assertion is the **gap**, not the other way round.
72
+
73
+ All non-chosen assertions are **gap**, reason "the CLI takes one pass condition". Suggest adding them as run criteria in the app, where several aggregate checks are allowed.
74
+
75
+ **If no source assertion matches any category** (e.g. only `forAll`/`details(...)` per-request assertions, or only `responseTime().max()` which has no CLI metric), emit **no `--pass-if` flag at all** — this is correct and expected, not a defect. Record every assertion as a gap and tell the user to add run criteria in the app. A `run.sh` with zero `--pass-if` is valid.
76
+ | Gatling | `--pass-if` | Notes |
77
+ |---|---|---|
78
+ | `global().failedRequests().percent().lt(x)` | `less_than(error_rate, x)` | approximated (see error-rate note below) |
79
+ | `global().failedRequests().count().is(0)` | `less_than_eq(error_rate, 0)` | approximated (percentage instead of count; + error-rate note) |
80
+ | `global().successfulRequests().percent().gt(x)` | `less_than(error_rate, 100 - x)` | approximated (see error-rate note below) |
81
+ | `global().responseTime().percentile(95).lt(ms)` (Scala `percentile3`) | `less_than(p95, ms)` | mapped (also p90, p99. Other percentiles → the nearest, approximated) |
82
+ | `global().responseTime().mean().lt(ms)` | `less_than(avg, ms)` | mapped |
83
+ | `global().responseTime().max()` / `min()` / `stdDev()` | none | gap |
84
+ | `global().requestsPerSec().gt(r)` | `greater_than(rps, r)` | mapped |
85
+ | `forAll…` / `details("group", "req")…` | none (no per-request criteria) | gap |
86
+ | `lte` / `gte` | `less_than_eq` / `greater_than_eq` | — |
87
+
88
+ **Error-rate note (why failed/successful-request mappings are approximated).** Gatling's `failedRequests` counts a request as failed when a **check** fails, even on an HTTP 200. Postman's `error_rate` counts transport/runtime errors and may **not** count a request that returned a response but failed a translated `pm.test` (behaviour differs by runner). So a batch of 200s that all fail a JSON check would fail Gatling's gate but can **pass** `less_than(error_rate, …)`. Map `failedRequests`/`successfulRequests` → `error_rate` as **approximated**, say so in MIGRATION.md, and note it's a weaker gate than Gatling's (plus any hosted-runner limitation). If the failure the user cares about is a check, a latency percentile or `rps` criterion may be the more faithful gate.
@@ -0,0 +1,91 @@
1
+ # Mapping: requests, EL, checks, protocol, flow, custom code
2
+
3
+ ## Protocol settings (`proto:*`)
4
+ | Gatling | Postman | Status |
5
+ |---|---|---|
6
+ | `.baseUrl("https://h")` | collection variable `baseUrl`; requests use `{{baseUrl}}/…` | mapped |
7
+ | `.baseUrls(a, b)` | first one as `baseUrl`; note the round-robin | approximated |
8
+ | `.header(k, v)`, `.headers(map)` | header on every request (or collection pre-request `pm.request.headers.upsert`) | mapped |
9
+ | `.acceptHeader`, `.contentTypeHeader`, `.userAgentHeader`, `.authorizationHeader` … | the matching header on every request | mapped |
10
+ | `.basicAuth(u, p)` | collection auth `basic`, with `{{user}}` / `{{password}}` from the environment | mapped |
11
+ | `.shareConnections()` | none. The runtime manages connections | gap (no-op; performance may differ) |
12
+ | `.enableHttp2()` | none. HTTP/1.1 only | gap |
13
+ | `.inferHtmlResources()` | none. Embedded resources are not fetched | gap |
14
+ | `.disableCaching()`, `.disableFollowRedirect()`, `.maxConnectionsPerHost()` | none, or the request setting `followRedirects: false` | gap / mapped for redirects |
15
+ | `.warmUp(url)` | none | gap |
16
+
17
+ If a header value is a secret (an inventory `secret` item, or a name like `x-api-key` or `authorization` with a literal), use `{{<camelCaseName>}}` and add an empty environment key.
18
+
19
+ ## Requests (`req:*`)
20
+ - Name: keep the `http("…")` name exactly.
21
+ - Method and path: `.get("/p")` → `GET {{baseUrl}}/p`. An absolute URL stays absolute.
22
+ - `.queryParam(k, v)` → a URL query param. `.formParam(k, v)` → `body.mode: urlencoded`. `.body(StringBody("…")).asJson()` → `body.mode: raw`, `options.raw.language: json`, plus header `Content-Type: application/json`.
23
+ - `.header(k, v)` on a request → that request's header.
24
+ - `ElFileBody("x.json")` / `RawFileBody` → inline the file contents into the raw body (translate EL inside it).
25
+
26
+ ## EL → Postman variables
27
+ | Gatling EL | Postman |
28
+ |---|---|
29
+ | `#{name}` | `{{name}}` |
30
+ | `#{randomUuid()}` | `{{$guid}}` |
31
+ | `#{randomInt()}` / `#{randomInt(a,b)}` | `{{$randomInt}}` (0–1000, approximated), or a pre-request script for a range |
32
+ | `#{randomLong()}` | a pre-request script, stored in a variable |
33
+ | `#{currentTimeMillis()}` | `{{$timestamp}}` is seconds, so use a pre-request script: `pm.variables.set('now', Date.now())` |
34
+ | `#{name.size()}`, `#{name(0)}`, `#{name.random()}` | a pre-request script over the parsed variable |
35
+ | `#{name.jsonStringify()}` | a pre-request script `JSON.stringify(...)` |
36
+ | `#{i}` from `repeat(n, "i")` | see Flow → loops |
37
+
38
+ Every `{{var}}` must be defined somewhere: the environment, collection variables, a data-file column, or set by an earlier script. The self-verify step checks this.
39
+
40
+ ## Checks (`check:*`) → test script
41
+ Put the checks in the request's `test` event, in source order. Name each `pm.test` after what it asserts.
42
+ | Gatling | Test script |
43
+ |---|---|
44
+ | `status().is(200)` (Kotlin: `shouldBe`) | `pm.test('status is 200', () => pm.response.to.have.status(200));` |
45
+ | `status().in(200, 204)` | `pm.expect([200,204]).to.include(pm.response.code)` |
46
+ | `jsonPath("$.a.b").is("x")` | `pm.expect(pm.response.json().a.b).to.eql('x')`. Gatling compares as a string unless `ofInt()` etc. is used, so compare `String(v)` when the source is a string literal |
47
+ | `jsonPath(...).isEL("#{v}")` | compare with `pm.variables.get('v')` (see **Saved variables scope**: read with `pm.variables.get`, write with `pm.collectionVariables.set`) |
48
+ | `jsonPath(...).exists()` / `.notExists()` | `pm.expect(value).to.not.be.undefined` / `.to.be.undefined` |
49
+ | `jsonPath(...).saveAs("v")` | `pm.collectionVariables.set('v', value)`, and also a `pm.test` that the value exists. **approximated**: Gatling marks the request failed on a missing extraction but, by default, **continues** the scenario — so do **not** add `pm.execution.setNextRequest(null)` here; that would skip the rest of the iteration even though the source doesn't. Stop early only when the source wraps this in `exitHereIfFailed`/`exitBlockOnFail` (see Flow control); if a later request needs to know the extraction failed, track that in a variable. Manifest target: the request's test script (`collection:<req> › test: saveAs v`), **not** `collection-variable:` — the value is script-set, not a declared collection variable |
50
+ | `jmesPath("a.b")` | same as jsonPath, using a JS property path |
51
+ | `regex("p").saveAs("v")` | `const m = pm.response.text().match(/p/); pm.test(..., () => pm.expect(m).to.not.be.null); pm.collectionVariables.set('v', m && m[1] !== undefined ? m[1] : m && m[0])`. Gatling saves group 1 if there is one, otherwise the whole match |
52
+ | `header("h").is(v)` | `pm.expect(pm.response.headers.get('h')).to.eql(v)` |
53
+ | `bodyString().is/contains` | `pm.response.text()` |
54
+ | `substring("s")` | `pm.expect(pm.response.text()).to.include('s')` |
55
+ | `responseTimeInMillis().lt(n)` | `pm.expect(pm.response.responseTime).to.be.below(n)` |
56
+ | `css(...)` / `xpath(...)` | cheerio (`require('cheerio')`) for CSS if the selector is simple. XPath → gap |
57
+ | `checkIf(cond)` | wrap the assertion in an `if` that translates the condition. Approximated if the condition is custom code |
58
+
59
+ **Implicit status check (`implicit-status:*`).** When a Gatling request declares no `status()` check, Gatling still fails it unless the status is 2xx or 304. Add `pm.test('status is 2xx/304 (Gatling implicit check)', () => pm.expect(pm.response.code === 304 || (pm.response.code >= 200 && pm.response.code < 300)).to.be.true);`. Status: mapped.
60
+
61
+ **Saved variables scope.** For correlation, **write** with `pm.collectionVariables.set('v', value)` and **read** with `pm.variables.get('v')` (the general resolver sees collection scope, so the two are consistent — don't mix `pm.collectionVariables.get` and `pm.variables.get` for the same value). In a performance run each virtual user has its own variable scope, which matches Gatling's per-user `Session`. A value saved this way is set by a script at run time, so it is **not** a declared collection variable — in the MIGRATION.md coverage table a `saveAs` lands on the request's test script, not a `collection-variable`.
62
+
63
+ **Name collisions with feeder columns.** Data-file (`iterationData`) values outrank collection variables when `{{x}}` / `pm.variables.get('x')` resolves (see `postman:variables`). So if a `saveAs`/session key has the **same name as a feeder column**, `pm.collectionVariables.set('x', …)` will **not** override it — `{{x}}` still resolves to the feeder's value, unlike Gatling where the later session write wins. When a saved key collides with a feeder column, **rename** the saved key (or the column) and use the renamed key consistently across the extraction, any custom-code translation, and the request substitutions; note the rename in MIGRATION.md.
64
+
65
+ ## Scenario, groups (`scenario:*`, `group:*`)
66
+ - Scenario → the collection itself (name the collection after it). If `setUp` has several scenarios, convert the main one and list the others as a gap: one collection per run.
67
+ - `group("g")` → a folder named `g` containing the requests in the group. Status: approximated. Postman reports per-request metrics, and there's no folder-level (transaction) timing. Say so.
68
+
69
+ ## Flow control (`flow:*`)
70
+ | Gatling | Postman | Status |
71
+ |---|---|---|
72
+ | `exec` chain (sequential) | request order in the collection | mapped |
73
+ | `repeat(n) { … }` / `repeat(n, "i")` | Use **one** request with a `setNextRequest` loop + a counter collection variable, so the single leaf runs n times. **Never unroll into n copies that share the same name**: a Postman collection can't hold two request leaves with the same path — so the "copies named `name`" shortcut is invalid. The loop also keeps the metric name identical (which is what unrolling was for). Only unroll if the body genuinely differs per iteration, and then give each copy a **distinct** name and note in MIGRATION.md that per-iteration metrics are split across those names. For `repeat(n, "i")`, set the counter as `{{i}}` in the loop script. **When the body is a chain of several requests** (`repeat(3){ A; B }`), loop the **whole body**, not one leaf: keep A and B as distinct requests and have the body's **last** request jump back to the **first** (`pm.execution.setNextRequest('A')`) while the counter < n, initialising/resetting the counter on first entry — so the order is A,B,A,B,A,B. A single-leaf loop only models a single-request body | approximated (explain the loop/metrics effect) |
74
+ | `during(d) { … }` / `forever { … }` wrapping the main journey | The VU loop itself: a VU repeats the collection until the run ends | approximated (Gatling users in open models start and stop. Here VUs loop) |
75
+ | `asLongAs(cond)`, `doWhile` | `setNextRequest` loop with a condition in the test script | approximated |
76
+ | `doIf(cond) { r }` | in the pre-request of `r`: `if (!cond) pm.execution.skipRequest()`. The condition is translated to JS (session attribute → `pm.variables.get`) | **mapped** if the condition reduces to a simple equality/comparison on a session value — *even when written as a lambda* (e.g. `session -> session.getString("tier").equals("gold")` → `pm.variables.get('tier') === 'gold'`). **approximated** only if it calls out to logic you can't express as a plain JS comparison (library calls, computation, external state). A lambda by itself does not force "approximated"; judge the body |
77
+ | `doIfOrElse`, `doSwitch`, `randomSwitch`, `roundRobinSwitch` | `skipRequest` logic per branch. `randomSwitch` percentages via `Math.random()` in a pre-request | approximated |
78
+ | `tryMax(n) { r }` | none. A request that fails isn't retried | gap. Suggest checking the error rate instead |
79
+ | `exitHereIfFailed` | in the test script: `if (pm.response.code >= 400) pm.execution.setNextRequest(null)` — ends the whole iteration | approximated (only stops on status failure, not on any failed check, unless you track that in a variable) |
80
+ | `exitBlockOnFail { … }` | exits **only its block**, then resumes after it — so on failure jump to the **first request after the block** (`pm.execution.setNextRequest('<next>')`), **not** `null`. Ending the iteration would skip requests/cleanup that sit outside the block | approximated (status-failure only unless you track check failures in a variable) |
81
+ | `stopLoadGenerator` / `crashLoadGenerator` | none | gap |
82
+
83
+ ## Custom code (`code:*`)
84
+ `exec(session -> …)` (Java), `exec { session -> … }` (Kotlin), `exec(session => …)` (Scala, JS):
85
+ - Translate it to a **pre-request script** on the next request.
86
+ - `session.set("k", v)` → `pm.collectionVariables.set('k', v)`
87
+ - `session.getString("k")` → `pm.collectionVariables.get('k')`
88
+ - `session.userId()` → `pm.info.iteration`, as an approximation
89
+ - `UUID.randomUUID()` → `crypto.randomUUID()` if available, otherwise `pm.variables.replaceIn('{{$guid}}')`
90
+ - Always prefix it with `// REVIEW: translated from Gatling custom code (<id>)`. Status: approximated. List it in MIGRATION.md → Approximations, with the original code.
91
+ - JVM library calls you can't translate (JDBC, crypto libraries, file IO) → gap.
@@ -0,0 +1,31 @@
1
+ # What can't be carried over, and how to say so
2
+
3
+ Postman performance runs are **HTTP(S) only** (GraphQL over HTTP and mTLS work). Load settings are the CLI flags in `injection.md`.
4
+
5
+ ## Always a gap
6
+ | Construct | Inventory kind | Wording for MIGRATION.md |
7
+ |---|---|---|
8
+ | `ws(...)`, `sse(...)` | `non_http_step` | WebSocket/SSE steps aren't part of Postman performance runs. They were removed from the load test. Test the socket flow separately (Postman's WebSocket client works for functional checks). |
9
+ | `grpc(...)`, `jms(...)`, `mqtt(...)`, `kafka(...)`, `amqp(...)` | `non_http_step` | This protocol isn't supported under load in Postman. These steps were removed. |
10
+ | `rendezVous(n)` | `timing` | There's no way to synchronise virtual users. Users won't wait for each other. |
11
+ | `pace(d)` | `timing` | There's no iteration pacing: VUs start the next iteration immediately. Add an explicit delay in the last request's test script only if the pacing matters (`setTimeout` isn't reliable under load, so prefer lowering `--vu-count`). |
12
+ | `throttle(...)` | `throttle` | There's no RPS cap. Throughput isn't limited to the Gatling target. |
13
+ | `tryMax(n)` | `flow` | Failed requests aren't retried. |
14
+ | `stopLoadGenerator`, `crashLoadGenerator` | `flow` | There's no scripted run abort. Use `--pass-if` to fail the run afterwards. |
15
+ | `.randomized()` on injection | `injection` | Arrivals aren't randomised. |
16
+ | `.enableHttp2()`, `.inferHtmlResources()`, `.shareConnections()` | `protocol_setting` | See `mapping.md`. |
17
+ | second and later assertions | `assertion` | The CLI takes one `--pass-if`. |
18
+ | `forAll` / `details(...)` assertions | `assertion` | There are no per-request pass criteria. |
19
+ | JDBC/Redis feeders | `feeder` | Export to CSV first. |
20
+ | extra scenarios in `setUp` | `scenario` | One collection per run. Convert them separately and run them in parallel. |
21
+
22
+ ## Pauses
23
+ `pause(d)` and `pause(min, max)` (`timing`) have no first-class think time. Always a **gap**; how it affects the result depends on the injection model:
24
+ - **Open model:** the pause is part of the journey time, so it already feeds the Little's-law `--vu-count` arithmetic in `injection.md`. Dropping the think time alone (keeping the computed VU count) means VUs send faster than Gatling users did. Say which metric (rps) to compare with the Gatling baseline.
25
+ - **Closed model:** there is no journey-time arithmetic to fold into — the VU count is the source concurrency directly. The pause is simply dropped: VUs loop with no think time, so they issue more requests per VU than Gatling did. Record it as a gap and tell the user throughput (rps) will be higher than Gatling's; they can lower `--vu-count` to compensate.
26
+ - If the user explicitly wants think time: a pre-request script with a busy-wait isn't acceptable under load. Record it as a gap, and suggest lowering `--vu-count` in proportion.
27
+
28
+ ## Wording rules
29
+ - One bullet per id: `` `<id>` ``, what changed, why, what to do. Keep it to 1–2 lines.
30
+ - Never write "not supported yet" or promise a roadmap. State today's behaviour.
31
+ - If the gap changes the results (pauses, pace, throttle, queue feeders, open-model injection), say which metric to compare with the Gatling baseline.
@@ -9,7 +9,7 @@ MCP exposes workspace resources directly to an agent. Existing collections,
9
9
  specs, examples, and environments provide maintained API knowledge; fetching
10
10
  the relevant entity preserves that context when a local CLI workflow is not
11
11
  available. For repository edits and Git review, prefer the local workflows in
12
- **api-engineer** and **bootstrap** when available.
12
+ **api-engineer** when available.
13
13
 
14
14
  Each tool definition exposed to the model consumes context whenever it is
15
15
  included in a request. Prefer the smallest available toolset that covers the
@@ -47,8 +47,8 @@ postman environment var set api_key --secret-id <secret-id> --vault-id <vault-id
47
47
 
48
48
  The environment then carries the vault/secret IDs rather than the value.
49
49
  Secret resolution and permission to view the secret are distinct; a teammate's
50
- ability to run a request need not expose the credential to them. Use
51
- **bootstrap** when the vault operation needs unresolved authentication.
50
+ ability to run a request need not expose the credential to them. Vault
51
+ operations require a signed-in CLI.
52
52
 
53
53
  ## Verification
54
54
 
@@ -23,7 +23,7 @@ how to use it:
23
23
  to fire it again.
24
24
 
25
25
  The command is singular, and every subcommand needs a signed-in session. Run what you need,
26
- and read `bootstrap` only if a command fails with a setup error such as `Authentication required`.
26
+ and sign in only if a command fails with a setup error such as `Authentication required`.
27
27
  When all you have is the URL, its subdomain is the webhook id.
28
28
 
29
29
  For capture or inspection alone, create or reuse a webhook and inspect its
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: workspaces
3
+ description: Use when initializing Postman in a repository, binding it to a Postman workspace, interpreting what `postman init` did, or when a CLI command fails because the plan, credits, or usage quota don't allow the operation. Treats the workspace as the place where a service's API work and its evidence live, kept separate from the services it depends on. Use for requests such as "set up Postman in this repo" or "connect this repo to our workspace".
4
+ ---
5
+
6
+ # Workspaces
7
+
8
+ A Postman workspace is where a service's API work happens and where its context
9
+ builds up: the contracts, collections, environments, and mocks, plus the runs
10
+ and results that show what was checked. It lives in Git beside the code, works
11
+ the same on a laptop or a build agent, and is shared when the work should be
12
+ seen. People use it to audit what was done, by whom, on whose behalf, and with
13
+ what result. Agents use it to read verified work instead of re-deriving it.
14
+
15
+ 1. `postman init` is the starting point. It gives the repository its place of
16
+ work: Postman files beside the code and, when that is the intent, a
17
+ workspace bound to them. `postman status` shows whether that is already
18
+ done, so init runs only when it adds something. Init is safe to repeat and
19
+ reports what it did: a binding it marks as inferred is a candidate contract
20
+ to confirm, and a partial result, with local files written but no workspace
21
+ created, is progress to inspect rather than redo.
22
+ 2. One workspace holds the context of one service: one Git repository, or in a
23
+ monorepo one remote URL and the subpath of one service.
24
+ `.postman/resources.yaml` records that owner, so every skill, CI job, and
25
+ teammate resolves the same context. A confirmed binding is worth reusing;
26
+ replacing it sends the work, and its evidence, to the wrong service.
27
+ 3. Other services keep their own workspaces. Their collections, environments,
28
+ and mocks come in as dependencies through **api-discovery**, so this
29
+ workspace reuses what another team maintains without taking it over. The
30
+ context graph links the two, which is how a change on one side reaches work
31
+ on the other.
32
+
33
+ ## Claiming the work
34
+
35
+ An agent can start in a guest session, and the work it does there is kept.
36
+ When free credits or a plan limit stop an operation, retrying won't help; the
37
+ person claiming the work is what lets it continue. Wording such as "usage limit
38
+ exceeded", "usage quota has been exceeded", a limit "reached", "breached" or
39
+ "exceeded" for the account, team, or plan, or an operation that "does not allow
40
+ more than" or "requires at least" a stated amount signals one. A temporary rate
41
+ limit or a fixed per-resource validation cap does not. Check which session the
42
+ CLI is using:
43
+
44
+ - Guest session: tell the user, in its own paragraph, that signing up for a
45
+ Postman account claims the work done so far and adds the credits needed to
46
+ continue. Offer to start signup for them; the CLI can produce a link to hand
47
+ over and waits while they finish in the browser.
48
+ - Signed-in account: tell the user, in its own paragraph, that they need to
49
+ upgrade their plan at https://postman.postman.co/settings/team/billing.
50
+
51
+ ## Verification
52
+
53
+ Report the binding in place and what `postman init` did.
@@ -1,140 +0,0 @@
1
- ---
2
- name: bootstrap
3
- description: Use when resolving Postman CLI setup, authentication, or a repository workspace binding; initializing local API files; handling a CLI error that says an operation isn't allowed because of insufficient entitlements; or importing, pulling, pushing, syncing, or sharing workspace resources. Reuses existing context and chooses sync direction without duplicating a workspace or rebinding a dependency. Use for requests such as "set up Postman for this repo".
4
- ---
5
-
6
- # Bootstrap Postman for This Repo
7
-
8
- A local Postman project puts contracts, runnable requests, environments, and
9
- mocks beside implementation code so they can be inspected, diffed, and reviewed
10
- in a PR. A workspace binding connects that work to maintained shared context.
11
- Setup is useful when it resolves a missing capability or binding; a local API
12
- check does not need a new workspace merely to proceed.
13
-
14
- ## Start with postman init
15
-
16
- `postman init` is idempotent and safe to run in any project, including one
17
- already initialized. You can run it before API engineering work to scaffold the
18
- relevant `postman/` directories and make Postman skills available in
19
- `postman/skills` to guide the task. It establishes an agent-scoped session for
20
- local work without signing in; accessing existing workspace resources still
21
- requires authentication.
22
- See "Interpret initialization without losing partial success" below for flags
23
- and outcomes; creating or connecting a workspace depends on the task.
24
-
25
- ## Resolve only the missing setup
26
-
27
- Inspect `postman --version`, `.postman/resources.yaml`, and the relevant
28
- `postman/` files before installing or initializing. Existing files are clues,
29
- not proof that paths and workspace IDs still describe this service. Preserve
30
- confirmed bindings for downstream skills rather than rediscovering them.
31
-
32
- **api-engineer**, **api-documentation**, **api-mocking**, **api-testing**, and
33
- **ci-integration** depend on this skill only for unresolved setup, authentication,
34
- or bindings. After resolving those prerequisites, return to the requesting
35
- skill's workflow; local work with confirmed setup can skip bootstrap entirely.
36
-
37
- If the CLI is missing, use `npm install -g postman-cli` or the applicable
38
- [platform installer](reference/cli_installation.md). Update an existing CLI
39
- through its original installation route; installing a second copy can leave
40
- the shell invoking the old binary. If installation is blocked, report the
41
- constraint and use **postman-mcp-server** for supported hosted operations.
42
-
43
- Use `postman <resource> <action> -h` to resolve flags and defaults for the
44
- installed CLI; cached examples can describe another version. Compare versions
45
- when diagnosing a missing capability rather than making an update a universal
46
- prerequisite. Spec lint belongs to `postman spec lint`; the deprecated
47
- `postman api` surface is not interchangeable.
48
-
49
- Local file operations need no normal login. `postman init` uses an agent-scoped
50
- session, so logging in first adds no value to that branch. Authenticate when
51
- an operation needs existing workspace resources: use
52
- `postman login --with-api-key "$POSTMAN_API_KEY"` when the key is already in
53
- the environment, otherwise `postman login`. Keep credential values out of
54
- logs and committed files; **variables** owns request-secret handling.
55
-
56
- ## Owning workspace versus dependency
57
-
58
- Read `localResources` and `workspace.id` in `.postman/resources.yaml`. When
59
- working with an existing workspace, inspect its identity and elements with
60
- `workspace list --json` and `workspace get <id> --elements --json`.
61
- The owning workspace represents this repository's service; another service's
62
- resource is a dependency. That distinction determines which context can be
63
- pulled without replacing the repository's binding.
64
-
65
- | State and intended outcome | Operation |
66
- | --- | --- |
67
- | Existing local layout meets the task | Reuse its files and bindings |
68
- | This service has a workspace; work on it locally | `postman workspace pull <workspace-id>` |
69
- | Only record this service's Git binding | `postman workspace connect-git <workspace-id> [path]` |
70
- | Another service's collection, environment, or mock is needed | Use **api-discovery**'s dependency workflow |
71
- | New project with no existing workspace | `postman init --json`; add `--visibility personal` or `--visibility team` when creating a workspace is intended |
72
- | Bound workspace is authoritative | `postman workspace pull` |
73
- | Validated local changes should be published | Preview with `postman workspace diff --push-strategy default`, then `postman workspace push` |
74
-
75
- Inspect local changes before pulling so an import does not discard work.
76
- Creating a second workspace does not connect an existing one, and pushing to a
77
- workspace does not grant teammates access. If sharing requires a visibility
78
- or permission change, inspect that metadata and resolve the access change
79
- separately rather than creating a replacement.
80
-
81
- `workspace diff` is read-only; use the strategy planned for the push.
82
- The default push creates/updates entities without deleting unmatched ones.
83
- `--push-strategy force-sync` can delete entities absent locally and belongs
84
- only to explicitly requested mirroring with the shown deletions approved.
85
- A request to publish supplies consent for its scoped push; otherwise obtain
86
- consent after the diff is reviewable. See **ci-integration** for publishing
87
- reviewed changes after merge.
88
-
89
- ## Interpret initialization without losing partial success
90
-
91
- Run init inside the intended repository. It scaffolds Postman files and
92
- `.postman/resources.yaml`; API implementation and a starter contract are
93
- separate work, not prerequisites to setup.
94
-
95
- `--json` avoids interactive prompts. Without `--visibility` it skips workspace
96
- creation; with it, creation and binding are requested. Read `bindings` and
97
- `exitCode` from the JSON payload. A binding marked `inferred` is a candidate
98
- contract, not a confirmed source of truth. Exit 2 means multiple specs need
99
- selection with `--spec <path>`. Exit 5 means local files were written but
100
- workspace creation failed; inspect that partial result before retrying.
101
-
102
- ## Subscription or usage limit
103
-
104
- A CLI command can fail because the user's subscription does not allow the
105
- operation or its credits or usage quota are exhausted. There is no fixed
106
- message for this; recognize wording such as "usage limit exceeded", "usage
107
- quota has been exceeded", or "reached" or "breached" or "exceeded" a limit for the account,
108
- team, or a plan-governed operation. Also recognize plan-dependent caps phrased
109
- as an operation that "does not allow more than" or "requires at least" a stated
110
- amount. Do not classify a temporary rate limit or a fixed per-resource
111
- validation cap as a subscription limit. Retrying will not clear a subscription
112
- limit, so stop retrying and run `postman whoami` to see whose session the CLI
113
- is using.
114
-
115
- If it reports a guest session ("Using Postman as a guest", or
116
- `"status": "guest"` with `--json`), such as the one `postman init` creates
117
- without a login, tell the user - so do it explicitly and cleanly in a new paragraph,
118
- that they need to sign up for a Postman account to get more credits and continue work,
119
- which also keeps the work done as a guest.
120
- If you run it for them, `postman signup --json` prints a `claimUrl`
121
- to hand over and then waits until they finish signing up in the browser.
122
- Ask the user if they want you to initiate signup for them.
123
-
124
- If the whoami probe reports a signed-in account, then explicitly and cleanly in a new paragraph,
125
- tell the user that they need to upgrade their plan, and that they can do so at
126
- https://postman.postman.co/settings/team/billing.
127
-
128
- ## Verification
129
-
130
- Report the working CLI, relevant paths, and actual binding or remaining gap.
131
- For a pull, confirm the intended entities appeared locally; for a push, report
132
- created/updated entities and conflicts. Installation alone does not establish
133
- a usable project, and successful sync does not establish teammate access.
134
-
135
- ## Reporting CLI and skill gaps
136
-
137
- Concrete reports help fix recurring CLI failures and misleading skill guidance.
138
- When actual use exposes one, handle the user's task first, then report the gap
139
- with `postman feedback`. Include a sanitized reproduction and the expected and
140
- observed behavior; exclude secrets, user data, and proprietary content.
@@ -1,59 +0,0 @@
1
- # Postman CLI Installation
2
-
3
- A global install on `PATH`. npm works anywhere Node is available; the curl
4
- installer covers macOS, Linux, and WSL; and the PowerShell installer covers
5
- Windows. Whichever route put the binary there is the one to use again when
6
- updating it — mixing routes leaves two `postman` binaries and a `PATH`
7
- question.
8
-
9
- ## Install
10
-
11
- **npm (all platforms):**
12
-
13
- ```bash
14
- npm install -g postman-cli
15
- ```
16
-
17
- **macOS, Linux, and WSL (curl):**
18
-
19
- ```bash
20
- curl -o- "https://dl-cli.pstmn.io/install/unix.sh" | sh
21
- ```
22
-
23
- **Windows (PowerShell):**
24
-
25
- ```powershell
26
- powershell.exe -NoProfile -InputFormat None -ExecutionPolicy AllSigned -Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://dl-cli.pstmn.io/install/win64.ps1'))"
27
- ```
28
-
29
- ## Check for drift
30
-
31
- ```bash
32
- postman --version # installed
33
- npm view postman-cli version # latest published
34
- ```
35
-
36
- ## Update
37
-
38
- Run the same command that installed it — the npm, curl or PowerShell line
39
- above, whichever put the binary there. Using a different one leaves two
40
- `postman` binaries and a `PATH` question. Never `npm install -g` over a copy
41
- that came from the curl installer or a system package manager.
42
-
43
- The CLI has no self-update verb. `postman skills update` is a different
44
- thing: it refreshes a repository's committed `postman/skills/`, not the
45
- binary.
46
-
47
- ## Uninstall
48
-
49
- npm installations:
50
-
51
- ```bash
52
- npm uninstall -g postman-cli
53
- ```
54
-
55
- Other install methods: delete the `postman` binary from its install
56
- directory (`%USERPROFILE%\AppData\Local\Microsoft\WindowsApps` on Windows,
57
- `/usr/local/bin` on macOS/Linux/WSL).
58
-
59
- Source: https://learning.postman.com/docs/postman-cli/postman-cli-installation/