The Stoobly Javascript library provides convenient access to stoobly-agent API.
Node 18 or higher.
Install the package with:
npm install stoobly --save-dev
const Stoobly = require('stoobly');
Or using ES modules:
import Stoobly from 'stoobly';
Configures requests with origin https://docs.stoobly.com to specify a scenario. sessionId defaults to current time.
import Stoobly from 'stoobly';
const stoobly = new Stoobly();
const interceptor = stoobly.interceptor({
scenarioKey: '<SCENARIO-KEY>',
scenarioName: '<SCENARIO-NAME>', // If scenario name is used instead of key, it should be unique
urls: [{ pattern: new RegExp('https://docs.stoobly.com/.*') }]
});
const sessionId = interceptor.enable();
Configures requests with origin https://docs.stoobly.com to specify a scenario. After a session has started, change sessions with withSessionId().
import Stoobly from 'stoobly';
const stoobly = new Stoobly();
const interceptor = stoobly.interceptor({
scenarioKey: '<SCENARIO-KEY>',
sessionId: '<SESSION-ID>',
urls: [{ pattern: new RegExp('https://docs.stoobly.com/.*') }]
});
const sessionId = interceptor.enable();
interceptor.withSessionId('<NEW-SESSION-ID>');
Configures requests https://docs.stoobly.com/use-cases and https://docs.stoobly.com/getting-started to specify a scenario.
import Stoobly from 'stoobly';
const stoobly = new Stoobly();
const interceptor = stoobly.interceptor({
scenarioKey: '<SCENARIO-KEY>',
urls: [
{ pattern: 'https://docs.stoobly.com/use-cases' },
{ pattern: 'https://docs.stoobly.com/getting-started' }
]
});
interceptor.enable();
Record requests with specific policy, order, and strategy options:
import Stoobly from 'stoobly';
import {
RecordPolicy,
RecordOrder,
RecordStrategy,
// Additional constants available:
//
// InterceptMode, // mock, record, replay
// MockPolicy, // All, Found
// ReplayPolicy, // All
// TestPolicy, // All, Found
// TestStrategy, // Diff, Fuzzy, Custom
// FilterAction, // Exclude, Include
// RequestParameter // Header, BodyParam, QueryParam
} from 'stoobly/constants';
const stoobly = new Stoobly();
const interceptor = stoobly.interceptor({
urls: [{ pattern: 'https://docs.stoobly.com/use-cases' }],
record: {
policy: RecordPolicy.All,
order: RecordOrder.Overwrite, // Defaults to RecordOrder.Append
strategy: RecordStrategy.Full,
}
});
interceptor.withInterceptModeRecord().enable();
Stop recording requests (switch back to another mode, e.g., mock):
interceptor.withInterceptModeMock().enable();
Stop all interception (recording, mocking, etc.):
interceptor.disable();
cypressInterceptor()playwrightInterceptor()interceptor(), which patches fetch and XMLHttpRequest directlySet the Stoobly interception mode in your cypress.config.*:
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
env: {
STOOBLY_INTERCEPT_MODE: process.env.STOOBLY_INTERCEPT_MODE, // 'mock' | 'record' | 'replay'
},
},
})
import Stoobly from 'stoobly';
import { RecordPolicy, RecordOrder, RecordStrategy } from 'stoobly/constants';
const stoobly = new Stoobly();
const stooblyInterceptor = () => stoobly.cypressInterceptor({
mode: Cypress.env('STOOBLY_INTERCEPT_MODE'),
record: {
policy: RecordPolicy.All,
order: RecordOrder.Overwrite, // Defaults to RecordOrder.Append
strategy: RecordStrategy.Full,
},
urls: [{ pattern: '<URLS>' }],
});
describe('Scenario', () => {
beforeEach(() => {
// WARNING: if a synchronous request is used, this will cause Cypress to hang. See: https://github.com/cypress-io/cypress/issues/29566
// Example of a synchronous request: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Synchronous_and_Asynchronous_Requests#synchronous_request
const interceptor = stooblyInterceptor();
interceptor.withScenarioNameFromTest().enable();
// Use the following instead to record requests
// interceptor.withInterceptModeRecord().enable();
});
});
Key Points:
describe blockwithScenarioNameFromTest() can be used to derive a scenario name from the Cypress test title pathinterceptor.enable() must be called in beforeEach because it uses cy.intercept. cy.intercept gets reset before every test. See: https://docs.cypress.io/api/commands/intercept#:~:text=All intercepts are automatically cleared before every test.Recommended: define a Playwright fixture to manage the interceptor lifecycle per test. This keeps setup/teardown consistent and avoids repeating boilerplate.
// tests/fixtures/stoobly.js
import { test as base } from '@playwright/test';
import Stoobly from 'stoobly';
import { RecordPolicy, RecordOrder, RecordStrategy } from 'stoobly/constants';
const stoobly = new Stoobly();
const interceptor = stoobly.playwrightInterceptor({
mode: process.env.STOOBLY_INTERCEPT_MODE, // 'mock' | 'record' | 'replay'
record: {
policy: RecordPolicy.All,
order: RecordOrder.Overwrite, // Defaults to RecordOrder.Append
strategy: RecordStrategy.Full,
},
urls: [{ pattern: '<URLS>' }],
});
export const test = base.extend({
stooblyInterceptor: async ({ page }, use, testInfo) => {
interceptor
.withPage(page)
.withScenarioNameFromTest(testInfo)
.withTestTitle(testInfo.title);
await interceptor.enable();
await use(interceptor);
},
});
export const expect = test.expect;
Use the fixture in tests:
// tests/example.spec.js
import { test, expect } from './fixtures/stoobly';
test.beforeEach(async ({ page, stooblyInterceptor }) => {
// In the fixture, the scenario is set to the test path, to override:
// stooblyInterceptor.withScenarioName('<SCENARIO-NAME'>);
});
test('Scenario', async ({ page, stooblyInterceptor }) => {
await page.goto('https://docs.stoobly.com');
// Requests are already intercepted by the fixture
await expect(page).toHaveTitle(/Stoobly/);
});
You can also set up interception at the context level inside the same fixture if you need to capture extension or service worker traffic (see the withContext() section below).
import { test } from '@playwright/test';
import Stoobly from 'stoobly';
import { RecordPolicy, RecordOrder, RecordStrategy } from 'stoobly/constants';
const stoobly = new Stoobly();
const stooblyInterceptor = stoobly.playwrightInterceptor({
record: {
policy: RecordPolicy.All,
order: RecordOrder.Overwrite, // Defaults to RecordOrder.Append
strategy: RecordStrategy.Full,
},
urls: [{ pattern: '<URLS>' }],
});
test.describe('Scenario', () => {
test.beforeEach(async ({ page }, testInfo) => {
await stooblyInterceptor.withPage(page).enable();
// Use the following instead to record requests
// await stooblyInterceptor.withPage(page).withInterceptModeRecord().enable();
stooblyInterceptor.withTestTitle(testInfo.title);
});
});
Key Points:
describe blockwithPage() and withTestTitle() must be called in beforeEach() to update the page and test titles for each test because Playwright does not provide a global API to auto-detect test titlesBy default, Playwright intercepts requests at the page level using withPage(). To intercept requests from browser extensions, service workers, or all pages in a browser context, use withContext():
test.beforeEach(async ({ context, page }, testInfo) => {
await stooblyInterceptor
.withContext(context) // Intercept all requests in the browser context
.enable();
stooblyInterceptor.withTestTitle(testInfo.title);
});
Using withContext() enables:
Note: You can use withContext() alone, withPage() alone, or both together. When both are used, routes are applied to both the page and context, ensuring comprehensive request interception.
Run unit tests:
npm test
Run Cypress end-to-end tests:
npm run test:cypress
Run Playwright end-to-end tests:
npm run test:playwright
Full API documentation is available at: https://stoobly.github.io/stoobly-js/
To regenerate TypeDoc docs locally:
npx typedoc