Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Postman Scripting Sandbox — pm.* API toàn cảnh

Mở đầu — vì sao bài này quan trọng

Nếu bạn đã đi qua các bài trước về Pre-request Scripts và Tests Scripts, bạn sẽ nhận ra một điều: mọi dòng code bạn viết trong Postman — dù là trước khi gửi request hay sau khi nhận response — đều chạy trong một môi trường đặc biệt gọi là Postman Sandbox. Và cánh cửa duy nhất để bạn nói chuyện với môi trường đó chính là namespace pm.*.

Hãy hình dung thế này. Bạn viết pm.environment.get("token"), pm.response.json(), pm.expect(...), pm.sendRequest(...). Tất cả đều bắt đầu bằng pm.. Đây không phải là sự trùng hợp — pm là một object toàn cục (global object) mà Postman tiêm sẵn vào sandbox để bạn điều khiển toàn bộ vòng đời của một request: đọc/ghi biến, kiểm tra response, viết assertion, gọi request phụ, ghi log, mã hóa dữ liệu.

Vấn đề của rất nhiều học viên — và cả người đi làm 2-3 năm — là họ dùng pm.* theo kiểu "chép từ StackOverflow", không hiểu sandbox thực chất là gì, cái gì được phép và cái gì bị cấm, và các nhánh của pm liên quan với nhau ra sao. Hậu quả là khi script chạy sai, họ không biết bắt đầu debug từ đâu.

Bài này là bài "bản đồ toàn cảnh". Sau khi học xong, bạn sẽ có trong đầu một sơ đồ rõ ràng: sandbox là một môi trường JavaScript cô lập (isolated), và pm.* là bộ API duy nhất để tương tác với nó. Bạn sẽ biết cần dùng nhánh nào cho việc gì, thay vì dò dẫm.

Khái niệm cốt lõi

Sandbox là gì và tại sao nó bị cô lập

Postman Sandbox là một môi trường thực thi JavaScript được xây trên nền Node.js (cụ thể là dự án postman-sandbox mã nguồn mở của Postman). Script của bạn chạy trong đó theo chuẩn ES6+, nhưng không phải là Node.js đầy đủ. Đây là điểm cực kỳ quan trọng cần khắc cốt ghi tâm:

  • Không có truy cập filesystem: bạn không thể require('fs') để đọc/ghi file trên máy. Không có fs.readFileSync.
  • Không có network trực tiếp: bạn không thể mở socket tùy ý. Cách duy nhất gọi ra mạng bên ngoài là qua pm.sendRequest() — một cổng có kiểm soát do Postman cung cấp.
  • Không có process, không có biến toàn cục tùy tiện tồn tại giữa các request (trừ khi bạn cố tình lưu qua pm.variables hoặc pm.globals).
Sự cô lập này không phải để làm khó bạn. Nó bảo vệ máy của bạn (và server Postman Cloud khi chạy Monitor) khỏi việc một collection lạ có thể quét ổ cứng hay tấn công mạng nội bộ. Bạn hãy coi sandbox như một "phòng sạch": chỉ những dụng cụ Postman đưa vào (pm, một số thư viện built-in như lodash, moment, cheerio, ajv, crypto-js) mới dùng được.

pm — object gốc và các nhánh của nó

Toàn bộ khả năng của sandbox được gom vào một object duy nhất: pm. Dưới đây là bản đồ các nhánh chính mà bạn sẽ gặp đi gặp lại:

Nhánh pm.*Vai tròVí dụ điển hình
pm.infoThông tin về lần chạy hiện tạipm.info.requestName, pm.info.iteration, pm.info.eventName
pm.requestRequest sắp gửi (đọc/sửa được ở pre-request)pm.request.url, pm.request.headers.add(...)
pm.responseResponse vừa nhận (chỉ có ở test script)pm.response.json(), pm.response.code, pm.response.responseTime
pm.environmentBiến của Environment đang chọnpm.environment.get/set/unset
pm.collectionVariablesBiến cấp Collectionpm.collectionVariables.set(...)
pm.globalsBiến toàn cục, dùng chung mọi nơipm.globals.get(...)
pm.variables"Bể gộp" ưu tiên — đọc biến theo scopepm.variables.get(...), pm.variables.replaceIn(...)
pm.vaultKho secret an toàn (mã hóa cục bộ)await pm.vault.get("apiKey")
pm.test / pm.expectViết test case + assertion (nền Chai.js)pm.test("...", () => pm.expect(x).to.eql(y))
pm.sendRequestGửi request phụ bất đồng bộpm.sendRequest(url, cb)
pm.cookiesĐọc cookie theo domainpm.cookies.get("session_id")
pm.executionĐiều khiển luồng (skip test, dừng run)pm.execution.setNextRequest(...)
pm.iterationDataDữ liệu dòng hiện tại khi chạy data filepm.iterationData.get("email")
Điểm chốt cần nhớ: pm.requestpm.response không cùng tồn tại ở mọi nơi. Ở Pre-request Script, bạn có pm.request (để sửa request) nhưng chưapm.response (vì chưa gửi). Ở Tests Script, bạn có cả hai. Đây là nguồn gốc của lỗi "Cannot read properties of undefined" mà người mới hay gặp.

Thứ bậc scope của biến — tại sao pm.variables.get lại "thần kỳ"

Postman có nhiều tầng biến, và pm.variables.get() sẽ đọc theo thứ tự ưu tiên từ hẹp đến rộng: Data (iteration) → Local → Environment → Collection → Global. Nghĩa là nếu cùng một tên baseUrl tồn tại ở cả Environment lẫn Global, pm.variables.get("baseUrl") trả về giá trị của Environment (hẹp hơn). Hiểu điều này giúp bạn tránh cảnh "tôi set biến rồi mà sao nó lấy giá trị cũ".

pm.expect chạy trên Chai.js

Assertion trong Postman không phải Postman tự chế — nó dùng thư viện Chai.js với cú pháp BDD (.to.have.status, .to.be.an('array'), .to.eql). Vì vậy kiến thức Chai bạn học ở đây tái sử dụng được sang cả Mocha/Jest sau này. Bài này chỉ giới thiệu vị trí của pm.expect trong sandbox; cách viết assertion sâu bạn đã gặp ở Bài 8.

Tình huống thực tế

Ví dụ 1 — Tiki và cái bẫy "gọi file trong sandbox"

Một bạn QA mới vào đội Marketplace của Tiki được giao viết bộ test cho API giỏ hàng. Bạn ấy muốn nạp 500 mã sản phẩm từ một file products.csv nằm trên máy, nên viết ngay trong Pre-request Script:

const fs = require('fs');            // BÁO LỖI
const data = fs.readFileSync('products.csv');

Chạy lên, Postman báo Error: Cannot find module 'fs'. Bạn ấy hoảng, tưởng Postman lỗi.

Diễn giải: Đây chính là biểu hiện của sandbox cô lập. Không có fs, không có filesystem. Cách đúng trong Postman là dùng data file gắn vào Collection Runner rồi đọc từng dòng qua pm.iterationData.get("productId"), hoặc nếu chỉ cần vài giá trị thì hardcode vào biến. File CSV/JSON được Postman nạp giúp qua Runner, chứ script không tự đọc ổ cứng.

Bài học rút ra: Trước khi viết bất cứ dòng nào có require, hãy tự hỏi "thư viện này có nằm trong danh sách built-in của sandbox không?". Chỉ vài cái tên như lodash, moment, crypto-js, ajv, cheerio, uuid, chai là dùng được. Còn lại — đặc biệt fs, http, child_process — đều bị chặn.

Ví dụ 2 — VNG và biến "biến mất giữa hai request"

Đội backend của VNG (mảng game publishing) có một flow: request A đăng nhập lấy authToken, request B dùng token đó để lấy danh sách vật phẩm. Một bạn junior lưu token trong Tests Script của A bằng:

let authToken = pm.response.json().token;   // biến JS thường

Sang request B, bạn ấy lại pm.request.headers.add({ key: 'Authorization', value: 'Bearer ' + authToken }) — và nhận lỗi authToken is not defined.

Diễn giải: let authToken chỉ là biến JavaScript cục bộ, sống trong đúng sandbox của một request rồi bị hủy. Sandbox không giữ biến JS thường giữa các request. Muốn truyền dữ liệu qua request khác, bạn phải "gửi gắm" nó vào một trong các kho của pm:

// Trong Tests của request A
pm.collectionVariables.set("authToken", pm.response.json().token);

// Trong Pre-request/Auth của request B const token = pm.collectionVariables.get("authToken");

Bài học rút ra: Sandbox là "không trạng thái" (stateless) giữa các request đối với biến JS thường. Mọi thứ cần sống dai phải nằm trong pm.environment, pm.collectionVariables hoặc pm.globals. Chọn scope nào tùy độ rộng: token theo môi trường thì để pm.environment; giá trị dùng chung cả collection thì pm.collectionVariables.

Ví dụ 3 — Startup fintech Đông Nam Á và pm.sendRequest để "làm nóng" dữ liệu

Một startup ví điện tử ở TP.HCM cần test API chuyển tiền. Trước mỗi lần test, họ phải tạo sẵn một giao dịch nháp qua một endpoint khác để lấy transactionId. Thay vì tách thành request riêng, kỹ sư QA dùng pm.sendRequest ngay trong Pre-request Script:

pm.sendRequest({
  url: pm.environment.get("baseUrl") + "/transactions/draft",
  method: 'POST',
  header: { 'Content-Type': 'application/json' },
  body: { mode: 'raw', raw: JSON.stringify({ amount: 50000 }) }
}, function (err, res) {
  if (err) { console.error(err); return; }
  pm.environment.set("transactionId", res.json().id);
});

Diễn giải: pm.sendRequestcổng mạng có kiểm soát duy nhất của sandbox. Nó bất đồng bộ, dùng callback (hoặc await với pm.sendRequest promisified). Nhờ nó, ta chuẩn bị dữ liệu ngay trong vòng đời request chính mà không cần dựng thêm bước trong workflow.

Bài học rút ra: Khi sandbox cấm network trực tiếp, pm.sendRequest chính là "van xả" hợp lệ. Nhưng hãy nhớ nó bất đồng bộ — nếu bạn pm.environment.set bên trong callback, đảm bảo request chính chỉ dùng biến đó sau khi callback đã chạy xong (Postman đợi callback hoàn tất trước khi gửi request chính, nhưng lỗi trong callback dễ bị nuốt nếu không log).

Hướng dẫn từng bước

Hãy tự tay khám phá sandbox để "nhìn thấy" pm bằng mắt thay vì tin lời.

Bước 1 — Mở Console. Vào một request bất kỳ, mở Postman Console (View → Show Postman Console, hoặc phím tắt Cmd/Ctrl + Alt + C). Đây là nơi mọi console.log trong sandbox hiện ra. Không có Console, bạn debug sandbox như đi trong bóng tối.

Bước 2 — In toàn cảnh pm. Vào tab Pre-request Script, gõ:

console.log(Object.keys(pm));
console.log("Info hiện tại:", pm.info);

Gửi request và xem Console. Bạn sẽ thấy danh sách các nhánh (environment, variables, request, sendRequest, test, expect...). Đây chính là "bản đồ" thực tế của phiên bản Postman bạn đang dùng.

Bước 3 — Xác nhận sự cô lập. Vẫn trong Pre-request, thử:

try { require('fs'); } catch (e) { console.log("Bị chặn:", e.message); }
console.log("moment có sẵn:", typeof require('moment'));

Bạn sẽ thấy fs bị chặn còn moment load được — bằng chứng sống động cho ranh giới của sandbox.

Bước 4 — Kiểm tra ngữ cảnh. Thêm vào Pre-request: console.log("Có response chưa?", typeof pm.response); — kết quả undefined. Rồi sang tab Tests thêm dòng tương tự — lần này là object. Bạn vừa tự chứng minh pm.response chỉ tồn tại sau khi gửi.

Bước 5 — Ghi và đọc biến xuyên nhánh. Trong Tests:

pm.collectionVariables.set("lastRunAt", new Date().toISOString());
console.log("Đọc lại:", pm.variables.get("lastRunAt"));

Chú ý ta ghi vào collectionVariables nhưng đọc qua pm.variables — và vẫn ra kết quả, vì pm.variables gộp mọi scope. Đây là cách bạn tận dụng thứ bậc scope.

Lỗi thường gặp & mẹo

  • Nhầm ngữ cảnh Pre-request vs Tests: Dùng pm.response trong Pre-request Script luôn cho undefined → lỗi. Luôn tự hỏi "code này chạy trước hay sau khi gửi?".
  • Tưởng biến JS thường sống dai: let x = ... biến mất giữa các request. Muốn giữ, phải dùng kho pm.environment/collectionVariables/globals.
  • Quên pm.sendRequest là bất đồng bộ: Đọc biến ngay sau pm.sendRequest (ngoài callback) sẽ ra giá trị cũ. Mọi xử lý phụ thuộc kết quả phải nằm trong callback (hoặc dùng await).
  • Lạm dụng pm.globals: Global sống mãi và dùng chung mọi collection → dễ "rò rỉ" dữ liệu bẩn sang test khác. Mẹo: ưu tiên pm.collectionVariables hoặc pm.environment, chỉ dùng global cho thứ thật sự toàn cục.
  • Bỏ quên pm.variables.unset / dọn dẹp: Token cũ, id cũ còn sót lại khiến test sau chạy sai. Mẹo: cuối flow, pm.collectionVariables.unset("transactionId") để trả sandbox về sạch.
  • Không mở Console: Đừng bao giờ debug script mà tắt Console. Mọi console.log, mọi lỗi runtime của sandbox đều hiện ở đó.
  • Mẹo secret: Đừng nhét API key thật vào pm.globals (nó lưu dạng thô, dễ lộ khi export). Với secret nhạy cảm, pm.vault là nhánh sinh ra để giữ chúng được mã hóa cục bộ.

Bài tập thực hành

  • Vẽ bản đồ sandbox của bạn: Trong một request bất kỳ, chạy console.log(Object.keys(pm)) ở cả Pre-request và Tests. Ghi ra giấy nhánh nào xuất hiện ở đâu, đặc biệt so sánh sự có mặt của pm.response.
  • Chứng minh cô lập: Viết đoạn thử require('fs'), require('crypto-js'), require('http') trong khối try/catch và log kết quả. Liệt kê 3 thư viện được phép và 3 thứ bị chặn.
  • Truyền dữ liệu xuyên request: Tạo 2 request A và B. Ở Tests của A, lưu một giá trị ngẫu nhiên (dùng pm.variables.replaceIn('{{$randomUUID}}')) vào pm.collectionVariables. Ở Pre-request của B, đọc lại và log ra. Xác nhận nó truyền được.
  • Dùng pm.sendRequest: Trong Pre-request của một request, gọi tới https://postman-echo.com/get, lấy một trường bất kỳ từ response và set vào pm.environment. Log để xác nhận callback đã chạy.
  • Thử scope: Đặt cùng tên biến env ở cả Environment và Global với giá trị khác nhau, rồi pm.variables.get("env"). Giải thích vì sao kết quả là giá trị của Environment.

Tóm tắt

Postman Sandbox là môi trường JavaScript cô lập nơi mọi script của bạn chạy: không filesystem, không network trực tiếp — cổng ra mạng duy nhất là pm.sendRequest. Toàn bộ khả năng tương tác được gom vào một object gốc pm, chia thành các nhánh có vai trò rõ ràng: pm.info (ngữ cảnh), pm.request/pm.response (dữ liệu request/response, tồn tại theo ngữ cảnh Pre-request vs Tests), họ biến pm.environment/pm.collectionVariables/pm.globals/pm.variables (với thứ bậc scope Data → Local → Environment → Collection → Global), pm.test/pm.expect (nền Chai.js), cùng các nhánh chuyên biệt như pm.vault, pm.cookies, pm.execution.

Ba điều cần khắc cốt: sandbox cô lập nên đừng gọi fs/http; biến JS thường không sống giữa các request nên phải dùng kho của pm; và luôn mở Console để nhìn thấy sandbox thay vì đoán mò. Nắm chắc bản đồ pm.* này, mọi bài script Postman về sau — dù là schema validation, chained request hay error handling — sẽ trở thành việc "chọn đúng nhánh cho đúng việc" thay vì mò mẫm.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi