QA How-To
Bruno vs Hoppscotch API Testing (2026)
Compare Bruno vs Hoppscotch API testing with runnable tests, CLI workflows, Git and workspace trade-offs, CI setup, and a practical 2026 verdict for teams.
20 min read | 3,302 words
TL;DR
Bruno is the stronger default for Git-centered engineering teams that want human-readable local collection files and pull-request review. Hoppscotch is better for teams that want a fast web client, shared workspaces, cloud or self-hosted collaboration, and easy access without installing a desktop app.
Key Takeaways
- Choose Bruno when collections should live as reviewable files beside application code and Git pull requests are the collaboration model.
- Choose Hoppscotch when instant browser access, shared workspaces, and an optional self-hosted collaboration service matter more than repository-native storage.
- Both tools support JavaScript assertions, environments, collection execution, nonzero CLI failures, and JUnit reports for CI.
- Bruno uses Chai assertions with res and bru APIs, while current Hoppscotch native scripts use the hopp namespace.
- Test the same authenticated workflow in both tools before choosing because request editing, secret delivery, and failure diagnosis drive long-term cost.
- Do not treat either functional collection runner as a replacement for contract, security, or performance testing tools.
Bruno vs Hoppscotch API testing is mainly a choice between two collaboration models, not a choice between a capable and an incapable HTTP client. Pick Bruno when the collection must be a first-class repository artifact reviewed through Git. Pick Hoppscotch when a browser-first workspace, shared service, and optional self-hosting create less friction for the people who explore and document APIs.
Both can send REST requests, manage environments, run JavaScript assertions, execute collections from a CLI, and fail a CI job. Their scripting names, storage formats, browser constraints, and team workflows differ enough to affect maintenance. The runnable comparison below sends the same requests to one local API so that the verdict rests on workflow evidence rather than feature-list marketing.
TL;DR
| Decision factor | Bruno | Hoppscotch |
|---|---|---|
| Primary working model | Local desktop client with collections stored on your filesystem | Web, desktop, cloud, and self-hosted clients with workspace collaboration |
| Recommended new collection format | OpenCollection YAML in Bruno 3 | Hoppscotch collection data, exportable for file-based CLI runs |
| Natural review path | Git diff and pull request | Shared workspace review or exported collection review |
| Native test API | test, Chai expect, res, and bru |
hopp.test, hopp.expect, and hopp.response |
| CLI command | bru run |
hopp test <collection> |
| CI reports | JSON, JUnit, and HTML | JUnit |
| Browser networking | Desktop process sends requests directly | Web client may need Agent, proxy, or extension for CORS and local access |
| Strongest fit | Code-owned API suites in repositories | Low-friction exploration and centrally shared workspaces |
Verdict: Start with Bruno if developers and SDETs already collaborate through branches, code review, and repository history. Start with Hoppscotch if product engineers, QA, support, and other API users need a shared browser workspace with minimal local setup. Keep an existing healthy collection where it is unless a measured workflow problem justifies migration.
What You Will Build
You will run equivalent smoke and authorization checks in both clients. The exercise deliberately includes more than a public GET because authentication, environments, CLI output, and failure behavior reveal the operational differences.
- Start a dependency-free Node.js API on
127.0.0.1:4010. - Check a health endpoint and a stable user representation.
- Protect an admin endpoint with an illustrative bearer token.
- Add native JavaScript assertions in Bruno and Hoppscotch.
- Export or save each collection, then execute it from the terminal.
- Produce JUnit files suitable for a CI test-results view.
The sample is small enough to finish in one sitting, but its boundaries mirror a real smoke suite. For a broader sequence of learning goals, use the API testing roadmap. If your organization needs a reusable JavaScript runner rather than a client collection, compare this exercise with the JavaScript API automation framework guide.
Prerequisites
Use Node.js 22 or later, npm, curl, Git, the current Bruno 3 desktop client, and the current Hoppscotch web or desktop client. Node 22 matters because current Hoppscotch CLI releases require it. Bruno's current CLI package is @usebruno/cli; Hoppscotch's is @hoppscotch/cli. Install both locally so the lockfile can control upgrades in a real repository.
mkdir api-client-comparison
cd api-client-comparison
npm init -y
npm install -D @usebruno/cli @hoppscotch/cli
node --version
npx bru --version
npx hopp --version
Verify that Node prints v22.x or a later supported major and that both CLIs print a version instead of an engine error. Do not copy a global CLI from a workstation into CI assumptions. A project-local dependency makes npx resolve the same package declared by the repository.
Create two directories, bruno/ and hoppscotch/, for the saved artifacts. Use the clients to create the actual collections in later steps. Keeping them separate prevents one tool's generated metadata from obscuring the comparison.
Step 1: Start a Neutral Sample API
Create server.mjs in the project root. It uses only Node's built-in HTTP module, returns deterministic JSON, and never persists data between requests. The hard-coded token is acceptable only for this throwaway local example.
import { createServer } from 'node:http';
const host = '127.0.0.1';
const port = 4010;
function sendJson(response, status, body) {
response.writeHead(status, {
'content-type': 'application/json; charset=utf-8',
'x-service': 'qa-sample'
});
response.end(JSON.stringify(body));
}
const server = createServer((request, response) => {
if (request.method === 'GET' && request.url === '/health') {
sendJson(response, 200, { status: 'ok', service: 'qa-sample' });
return;
}
if (request.method === 'GET' && request.url === '/users/1') {
sendJson(response, 200, { id: 1, name: 'Ada', role: 'admin' });
return;
}
if (request.method === 'GET' && request.url === '/admin') {
const authorized = request.headers.authorization === 'Bearer qa-token-2026';
sendJson(
response,
authorized ? 200 : 401,
authorized ? { scope: 'admin' } : { error: 'unauthorized' }
);
return;
}
sendJson(response, 404, { error: 'not_found' });
});
server.listen(port, host, () => {
console.log(`QA sample API listening at http://${host}:${port}`);
});
Start it in one terminal and keep that process running. In another terminal, verify both the positive and negative boundaries before opening either client.
node server.mjs
curl -i http://127.0.0.1:4010/health
curl -i http://127.0.0.1:4010/admin
curl -i -H 'Authorization: Bearer qa-token-2026' http://127.0.0.1:4010/admin
The status sequence should be 200, 401, and 200. The final body should be {"scope":"admin"}. If these checks fail, fix the service process or port before debugging a collection.
Step 2: Build the Bruno API Test
Open Bruno and create a collection named qa-api inside the bruno/ directory. Select YAML for the file format. Bruno 3 continues to support classic .bru files, but OpenCollection YAML is the recommended format for a new collection. The generated root file is opencollection.yml.
Create a request named Get User, choose GET, and set the URL to {{baseUrl}}/users/1. Save it as get-user.yml, then add the following test script in the Tests tab. Bruno exposes the completed response as res and uses Chai syntax through expect.
test('returns the seeded admin user', function () {
expect(res.getStatus()).to.equal(200);
expect(res.getHeader('content-type')).to.include('application/json');
expect(res.getBody()).to.deep.equal({
id: 1,
name: 'Ada',
role: 'admin'
});
});
test('responds within the smoke budget', function () {
expect(res.getResponseTime()).to.be.lessThan(1000);
});
The 1000 ms limit is an illustrative smoke threshold for a local process, not a benchmark claim. Click Send and inspect the Tests response panel. Both tests should be green, and the response headers should include x-service: qa-sample.
The saved YAML is readable outside Bruno. Its runtime.scripts section contains the same test code, so a reviewer can see an endpoint, expected domain data, and timing guard in one diff. That locality is Bruno's central advantage. Add changes to Git only after checking that environment or secret files are excluded by the generated .gitignore.
Step 3: Run Bruno Through Its CLI
Create bruno/qa-api/environments/local.json with Bruno's JSON environment schema. It provides the base URL without embedding a machine-specific address in the request.
{
"name": "local",
"variables": [
{
"name": "baseUrl",
"value": "http://127.0.0.1:4010",
"enabled": true
}
]
}
Run the collection from the directory containing opencollection.yml. Ask for JUnit and HTML reports so you can inspect machine and human views of the same run.
mkdir -p reports
cd bruno/qa-api
npx bru run \
--env-file environments/local.json \
--reporter-junit ../../reports/bruno.xml \
--reporter-html ../../reports/bruno.html
cd ../..
Verify that the command exits with zero, reports two passed tests, and creates both report files. Open the HTML artifact locally if you want request-level detail, but do not publish unsanitized reports from authenticated environments. Bruno also supports JSON output, tag inclusion and exclusion, iteration data, and parallel execution. Use --parallel only for requests that do not depend on state produced by an earlier sequence.
Bruno 3 runs scripts in Safe Mode by default. A collection that imports external npm packages or needs filesystem access requires an explicit --sandbox=developer run. Treat that switch as a security review point, not a routine fix, because it expands what collection code can do on a workstation or CI agent.
Step 4: Build the Hoppscotch API Test
Open Hoppscotch, create a collection named qa-api, and add a GET request named Get User. Set its URL to <<baseUrl>>/users/1. Hoppscotch uses double angle brackets for environment substitution, unlike Bruno's double braces. Create an environment with baseUrl set to http://127.0.0.1:4010 and select it.
Paste this native script into the request's Tests tab. Current Hoppscotch scripting exposes hopp.response.statusCode, a body wrapper with asJSON(), and Chai-compatible assertions through hopp.expect.
hopp.test('returns the seeded admin user', () => {
const user = hopp.response.body.asJSON();
hopp.expect(hopp.response.statusCode).to.equal(200);
hopp.expect(user).to.deep.equal({
id: 1,
name: 'Ada',
role: 'admin'
});
});
hopp.test('has the expected representation', () => {
const user = hopp.response.body.asJSON();
hopp.expect(user).to.have.all.keys('id', 'name', 'role');
hopp.expect(user.role).to.equal('admin');
});
Send the request. Two passing suites should appear in the test results. If the web client cannot reach localhost or reports a browser CORS failure, select the Hoppscotch Agent or browser extension interceptor, or use the desktop client. This is a browser security boundary, not an assertion defect.
Export the collection to hoppscotch/qa-api.json and export the selected environment to hoppscotch/local.json. Keep the export produced by the client rather than hand-authoring its internal collection schema. That approach survives schema evolution and mirrors the artifact the CLI officially accepts.
Step 5: Run Hoppscotch Through Its CLI
Execute the exported collection file with its environment. Hoppscotch recursively follows collection order, runs each request's test script, and returns a nonzero exit code when an assertion fails. It does not automatically treat every non-200 response as failure, so the test script must express the expected status.
mkdir -p reports
npx hopp test hoppscotch/qa-api.json \
--env hoppscotch/local.json \
--reporter-junit reports/hoppscotch.xml
Verify a zero exit code and confirm reports/hoppscotch.xml exists. The summary separates requests, test scripts, suites, cases, and their durations. In JUnit, each hopp.test() suite groups the hopp.expect() cases beneath a request-level suite. Give each expectation a meaningful surrounding test name so CI failures remain understandable.
Hoppscotch CLI can also execute a collection stored in a shared cloud or self-hosted workspace by collection ID, environment ID, personal access token, and server URL. It cannot run a collection from a personal workspace by ID. For a repository-owned pipeline, an exported file makes review and reproducibility explicit. For a centrally managed workspace, ID-based execution avoids manual export but adds availability, token rotation, and change-governance dependencies. Decide which source is authoritative before enabling both paths.
Step 6: Compare Authentication and Secrets
Add a second request named Get Admin to each collection. Its URL is /admin, and its Authorization header uses the environment token. In Bruno use Bearer {{apiToken}}; in Hoppscotch use Bearer <<apiToken>>. Add apiToken locally with the value qa-token-2026, rerun each request, and assert status 200 plus { scope: 'admin' }.
For Bruno, the test script is:
test('allows the configured admin token', function () {
expect(res.getStatus()).to.equal(200);
expect(res.getBody()).to.deep.equal({ scope: 'admin' });
});
For Hoppscotch, use its native namespace:
hopp.test('allows the configured admin token', () => {
const body = hopp.response.body.asJSON();
hopp.expect(hopp.response.statusCode).to.equal(200);
hopp.expect(body.scope).to.equal('admin');
});
Verify the protection rather than only the happy path. Temporarily remove the token value and send the request. The correct result is 401, and the positive assertion must fail. Restore the token, rerun the two CLI commands, and expect zero exits. This mutation check proves the collection is testing authorization instead of merely executing an endpoint.
Never commit the illustrative value when adapting the sample. Bruno CLI can accept overrides such as --env-var API_TOKEN=value; current Hoppscotch guidance recommends injecting secret values through the operating-system environment or supplying them in an environment export prepared securely for the run. Use the CI provider's secret store, mask request headers in reports, and restrict collection scripts from printing variables. The API test data management guide covers ownership and cleanup beyond credentials.
Step 7: Put Both Collection Runs in CI
Commit the server and intentionally reviewable collection artifacts. Keep private environments and generated reports ignored. The following GitHub Actions job starts the same local API, checks readiness, runs both CLIs, and uploads JUnit files even after a test failure. Replace the collection paths if your clients generated different folder names.
name: api-client-comparison
on:
pull_request:
jobs:
collections:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: mkdir -p reports
- name: Start sample API
run: |
node server.mjs > server.log 2>&1 &
for attempt in 1 2 3 4 5; do
curl --fail http://127.0.0.1:4010/health && exit 0
sleep 1
done
exit 1
- name: Run Bruno collection
working-directory: bruno/qa-api
run: npx bru run --env-file environments/local.json --reporter-junit ../../reports/bruno.xml
- name: Run Hoppscotch collection
run: npx hopp test hoppscotch/qa-api.json --env hoppscotch/local.json --reporter-junit reports/hoppscotch.xml
- uses: actions/upload-artifact@v4
if: always()
with:
name: api-junit-reports
path: reports/*.xml
Verify the workflow in a pull request. A successful job must show both runner steps green and contain two XML artifacts. Then change one expected role from admin to viewer on a branch. The associated runner should exit nonzero and display the assertion name. Revert that deliberate failure before merging. This controlled break is more valuable than trusting a green configuration that has never proved its failure path.
Bruno vs Hoppscotch API Testing: Storage and Collaboration
Bruno treats the filesystem as the collection database. In Bruno 3, a new YAML collection has an opencollection.yml root plus request, folder, and environment YAML files. Classic .bru collections remain supported. A normal Git branch captures request edits, script changes, documentation, and review comments alongside application code. That model works particularly well when API behavior changes in the same pull request as its smoke test.
The cost is that Git becomes the collaboration product. Non-engineers need enough repository access and review fluency to contribute safely. Merge conflicts are possible when several people edit the same request, although human-readable YAML or Bru files make conflict resolution more transparent than a large opaque export. Bruno's desktop Git UI provides core operations in the free release, while some advanced GUI Git operations depend on paid editions. Terminal Git remains an independent option.
Hoppscotch centers collaboration on workspaces. A team can use its hosted service or self-host Community or Enterprise editions, invite members, and operate from browser, desktop, or CLI clients. This reduces checkout and application-install friction, especially for support engineers or backend developers who need to reproduce a request quickly. The browser client may need an Agent, proxy, or extension when CORS or localhost access blocks direct requests.
Exports make Hoppscotch collections repository-compatible, but a team must prevent split authority. If a cloud workspace and a committed JSON export both change, define who exports, when the file is refreshed, and what CI runs. A stale export can make a passing pipeline disagree with the shared workspace.
Bruno vs Hoppscotch API Testing: Automation Trade-offs
Bruno provides the richer repository-native CLI surface. It can run a whole collection, selected files or folders, tags, iterations, CSV or JSON data, named or file-based environments, and parallel work. Built-in JSON, JUnit, and HTML reporters serve machines, CI dashboards, and local diagnosis. Safe Mode is a useful default for reviewing untrusted collection scripts; Developer Mode should require an explicit reason.
Hoppscotch CLI is deliberately focused on collection execution. hopp test accepts a local export or a shared-workspace collection ID, executes requests recursively in their defined order, supports environment inputs, iteration counts and data, delays, and JUnit reporting. Current releases require Node 22 or later. A network response with a 4xx or 5xx status still reaches the script, so omitting a status assertion can create a false pass.
The scripting APIs are not portable. Bruno tests call test() and Chai expect() against res; scripts share runtime data with bru.setVar() and related APIs. Hoppscotch native scripts call hopp.test() and hopp.expect() against hopp.response, while a Postman compatibility layer also exists. Importing a collection does not guarantee complete semantic migration, particularly for scripts, sandbox permissions, variable precedence, or third-party libraries.
Neither runner proves an OpenAPI contract merely by checking a few fields. Add dedicated schema validation for the response shapes consumers depend on, following JSON response schema validation. Use Pact API contract testing when independently released consumers and providers need compatibility guarantees.
Which Should You Choose
Choose Bruno when the API suite belongs to the same engineering lifecycle as the service. Strong signals include mandatory pull-request review, offline work, a policy that test assets remain on developer machines and CI, and a desire for small request-level diffs. It is also the cleaner choice when a team wants one local source of truth without operating a collaboration server.
Choose Hoppscotch when reach and shared access dominate. It works well for teams that want developers to open a browser, join a workspace, inspect a request, and collaborate without first cloning a collection repository. Self-hosting provides another route when the organization needs control of the service and collection data. Include administration, backups, upgrades, identity, and Agent deployment in that decision, because self-hosted software still has an operating cost.
Stay with the incumbent tool if it already has reliable CI runs, controlled secrets, understandable reports, and active ownership. A migration that only changes UI preference creates script translation work and review noise without reducing risk. Run the local exercise, then score both options from 1 to 5 for collection review, contributor access, offline use, secret delivery, CI reports, failure diagnosis, self-hosting, and maintenance. Weight the criteria before scoring so the result cannot be adjusted to favor a preferred brand.
Do not force all API quality into either client. Use a browser or API runner for functional flows, a contract tool for compatibility, a scanner for security, and a load generator for performance. The API security testing basics explain checks that collection assertions alone cannot cover.
Common Mistakes
- Comparing only a GET request. Add environment substitution, a negative status, authentication, a chained value, CLI execution, and report inspection before choosing. The hard workflow predicts maintenance better than a five-minute demo.
- Leaving status behavior implicit. Assert the exact expected status in both tools. Hoppscotch CLI still runs tests for non-200 responses, and a weak body check can accidentally accept an error payload.
- Mixing variable syntax. Bruno uses
{{baseUrl}}; Hoppscotch uses<<baseUrl>>. An imported URL that preserves the wrong delimiter will call a malformed or unresolved address. - Committing secrets with an environment. Keep safe defaults reviewable and inject credentials at runtime. Check generated reports, console logs, exports, and screenshots for authorization headers before sharing them.
- Assuming a UI feature works identically in CLI. Validate the exact protocol, authentication method, interceptor behavior, and script API in headless execution. A client can support an interactive operation that its collection runner handles differently.
- Enabling Bruno Developer Mode reflexively. Investigate which package or filesystem operation requires it. Broader sandbox access changes the trust boundary of code pulled from a repository.
- Ignoring browser CORS constraints in Hoppscotch. When curl succeeds but the web client fails, configure Agent, extension, proxy, or desktop routing instead of weakening the target API's CORS policy for a test tool.
- Maintaining two Hoppscotch sources of truth. If workspace IDs run in one pipeline and exported JSON runs in another, assign ownership and an export cadence or they will drift.
- Parallelizing a sequence with shared state. Authentication, creation, lookup, and deletion may require order. Parallelize independent folders or data partitions, not dependent requests.
- Treating collection concurrency as load testing. Functional runners add assertion and reporting overhead and do not control arrival rate precisely. Use a purpose-built performance tool for latency and capacity claims.
Troubleshooting
hopp reports an unsupported Node engine -> Install Node 22 or later, remove the old dependency tree, reinstall from the lockfile, and confirm node --version inside the same shell or CI step.
Hoppscotch web cannot call 127.0.0.1 -> Start the sample server, confirm curl works, then select Hoppscotch Agent, the browser extension, or the desktop client. Inspect the browser console for CORS or mixed-content evidence.
bru run cannot find a collection -> Run it from the directory containing opencollection.yml for YAML or bruno.json for classic Bru collections. Check that the request was saved inside that collection rather than as an unsaved tab.
An environment placeholder reaches the server unchanged -> Check the tool-specific delimiters and confirm the intended environment is selected or passed to the CLI. Print only the resolved base URL, never a token.
A 401 request passes in Hoppscotch CLI -> Add an explicit status expectation. HTTP error statuses are valid responses available to the script unless your assertions reject them.
CI passes locally but cannot reach the API -> Verify service readiness, network namespace, hostname, proxy, certificate trust, and secret availability. A container's 127.0.0.1 refers to that container, not automatically to another service.
Interview Questions and Answers
A credible comparison starts with architecture: Bruno favors repository files and Git review, while Hoppscotch favors accessible workspaces across web, desktop, hosted, and self-hosted deployments. Then explain test APIs, CLI failure semantics, secret handling, and how you would prove the choice with one representative flow. The structured interview answers below give concise models, but your strongest answer should include evidence from a collection you actually operated.
Where To Go Next
Extend the sample with one POST, capture its generated identifier, read it back, and delete it. That sequence exposes variable scope and cleanup behavior without adding artificial complexity. Add a malformed payload and a forbidden-role test so the suite protects more than availability.
Before production use, define collection ownership, review rules, environment naming, secret injection, report retention, and a maximum smoke runtime. Use the QA practice workspace to rehearse explaining the decision to an interviewer or architecture panel. If API design and browser automation are also under evaluation, the Playwright vs Cypress API testing comparison covers a different runner category.
Conclusion
Bruno vs Hoppscotch API testing has no universal winner. Bruno is the better default for teams that want API collections stored as local, human-readable repository files with direct Git review. Hoppscotch is the better default for teams that value browser access, centrally shared workspaces, and a cloud or self-hosted collaboration plane.
Run the same authenticated smoke flow in both, break one assertion deliberately, and review the resulting diff and CI artifact. Choose the workflow your team can secure, diagnose, and keep authoritative, then use specialized tools for risks beyond functional collection testing.
Interview Questions and Answers
How would you summarize Bruno versus Hoppscotch in an architecture review?
Bruno is repository-first: local request files, Git diffs, and pull-request collaboration are its natural model. Hoppscotch is workspace-first: web and desktop access, hosted or self-hosted collaboration, and shared collections are central. Both have scriptable tests and CI-capable CLIs, so I would choose based on ownership and governance rather than basic HTTP features.
How do Bruno and Hoppscotch tests assert responses?
Bruno exposes the response as res and uses test with Chai expect. Hoppscotch's native API uses hopp.test, hopp.expect, and hopp.response, including body.asJSON() for JSON payloads. I keep the status assertion explicit in both runners.
How would you prevent secret leakage in either tool?
I commit only safe environment values such as a test base URL and inject tokens from the CI secret store. I exclude private exports, redact Authorization and cookies from reports, and prohibit scripts from logging resolved secrets. I also test an invalid credential so secret injection does not become an unverified setup step.
What happens when Hoppscotch CLI receives a 500 response?
A non-200 response is still a response available to the test script unless a network error prevents execution. The script must assert the expected status or business error contract. I never assume the HTTP status alone will fail every collection run automatically.
When would you enable Bruno Developer Mode?
Only when reviewed collection scripts genuinely need external npm packages or filesystem access that Safe Mode blocks. I document that dependency and assess collection trust because Developer Mode expands script capabilities. A simple REST smoke suite should normally stay in Safe Mode.
How would you evaluate a migration from Hoppscotch to Bruno?
I would migrate one difficult workflow containing authentication, environment variables, pre-request logic, response capture, a negative case, and CI reporting. Then I would compare script semantics, diffs, contributor access, failure diagnosis, and secret handling. A simple GET import is not enough evidence for a full migration.
How do you avoid collection drift in Hoppscotch?
I designate either the shared workspace or the committed export as the source of truth. If CI runs exports, I assign an owner and refresh rule; if CI runs workspace IDs, I govern workspace changes and token rotation. Running both casually creates two histories that can disagree.
Frequently Asked Questions
Is Bruno better than Hoppscotch for API testing?
Bruno is usually better when collections must live in Git as human-readable files and change through pull requests. Hoppscotch can be better when browser access, shared workspaces, and cloud or self-hosted collaboration reduce contributor friction.
Can Hoppscotch collections run in CI?
Yes. The Hoppscotch CLI runs an exported collection file or an eligible shared-workspace collection ID and returns a nonzero exit code when assertions fail. It can also generate a JUnit XML report for the CI system.
Can Bruno collections run without the desktop app?
Yes. Install @usebruno/cli and run bru run from the collection directory. The CLI supports environments, tags, data files, iterations, parallel execution, and built-in JSON, JUnit, and HTML reports.
What is the scripting difference between Bruno and Hoppscotch?
Bruno tests use test and Chai expect with response data on res, plus bru APIs for variables and runner control. Current Hoppscotch native scripts use hopp.test, hopp.expect, and hopp.response, so scripts need translation rather than simple copying.
Why does Hoppscotch fail on localhost when curl works?
The web client is subject to browser CORS, mixed-content, and network rules that do not apply to curl. Use Hoppscotch Agent, the browser extension, a configured proxy, or the desktop client after confirming the local service is actually listening.
Does Bruno store API collections locally?
Yes. Bruno stores collections on the filesystem, with OpenCollection YAML recommended for new Bruno 3 collections and classic .bru files still supported. Teams can version those request files with normal Git workflows.
Can either tool replace contract or load testing?
No. Both are useful for functional requests and workflow assertions, but neither collection runner alone coordinates provider-consumer compatibility or generates controlled performance workloads. Use contract, security, and load tools for those distinct risks.