Replay Browser Requests for Multi‑Endpoint API Load Testing
This article explains how to capture all HTTP requests from a browser, filter out irrelevant assets, convert them into HttpRequestBase objects, and run a concurrent load test across multiple APIs using the FunTester framework, complete with code snippets and performance metrics.
Previously the author described a method for single‑API performance testing by replaying a browser request; this follow‑up shows how to extend the technique to test many endpoints in one run.
Copying Requests
All network requests are copied from the browser’s developer tools. The raw export contains many entries (HTML, JS, CSS, images, etc.), so the data must be filtered before testing.
Creating HttpRequestBase Objects
Each copied line is parsed into a CurlRequestBase object. When the parser encounters the "--compressed" marker, it immediately creates an HttpRequestBase instance, stores it, and resets the CurlRequestBase for the next request, ensuring requests remain independent.
public static List<HttpRequestBase> getRequests(String path) {
def fileinfo = WriteRead.readTxtFileByLine(LONG_Path + path).stream().map {it.trim()}
def requests = []
def base = new CurlRequestBase()
fileinfo.each {
if (it.startsWith("curl")) {
def split = it.split(" ", 2)
def type = split[0]
def value = split[1]
base.url = value.substring(value.indexOf('h'), value.lastIndexOf("'"))
} else if (it.startsWith("-H")) {
def split = it.split(" ", 2)[1].split(": ")
base.headers << FanLibrary.getHeader(split[0].substring(1), split[1].substring(0, split[1].lastIndexOf("'")))
} else if (it.startsWith("--data-raw")) {
base.params = getJson(it.substring(it.indexOf("'") + 1, it.lastIndexOf("'")).split("&"))
base.type = RequestType.POST
} else if (it.startsWith("--compressed")) {
requests << getRequest(base)
base = new CurlRequestBase()
}
}
requests.findAll {
it != null && it.getFirstHeader("accept").getValue().contains("application/json")
}
}
static HttpRequestBase getRequest(CurlRequestBase base) {
if (filterWords.any { base.url.contains(it) }) return
return base.type == RequestType.GET ?
FunRequest.isGet().setUri(base.url).addHeader(base.headers).getRequest() :
FunRequest.isPost().setUri(base.url).addHeader(base.headers).addParams(base.params).getRequest()
}Filtering Unwanted Requests
The parser discards static resources (JS, CSS, images, etc.) using a filterWords list and also removes any request whose Accept header does not contain application/json, ensuring only JSON API calls are kept.
public static def filterWords = [".js", ".png", ".gif", ".css", ".ico", "list_unread", ".svg", ".htm", ".jpeg", ".ashx"]
if (filterWords.any { base.url.contains(it) }) returnRunning the Load Test
The main script obtains the filtered request list, prints its size, creates a RequestThreadTime for each request with a 100 ms timeout, and executes them concurrently via a Concurrent runner.
public static void main(String[] args) {
def requests = getRequests("get")
output(requests.size())
def threads = []
requests.each {
output FanLibrary.getHttpResponse(it)
threads << new RequestThreadTime<HttpRequestBase>(it, 100, null)
}
new Concurrent(threads, "FunTester测试").start()
testOver()
}Sample Console Output
The test prints a JSON summary with fields such as response time (rt), total requests, QPS, fail rate, thread count, start/end timestamps, and a visual distribution of response times.
{
"rt":40,
"total":9993,
"qps":99.92275560021898,
"failRate":0.0,
"threads":4,
"startTime":"2021-01-27 15:37:43",
"endTime":"2021-01-27 15:39:23",
"errorRate":0.0,
"executeTotal":9993,
"mark":"FunTester测试20210127153743",
"table":"..."
}The distribution chart is best viewed with a monospaced font; a screenshot is provided for reference.
Resources
The FunTester framework source code is hosted on Gitee and GitHub:
Gitee: https://gitee.com/fanapi/tester
GitHub: https://github.com/JunManYuanLong/FunTester
For more details on using the testing framework and interpreting results, refer to the author’s earlier articles linked in the original post.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
