stoobly
    Preparing search index...

    stoobly

    Stoobly Node.js library

    Node.js Version npm version CI TypeScript Cypress Playwright License Docs

    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();
    
    • Using Cypress? See Integrating with Cypress and use cypressInterceptor()
    • Using Playwright? See Integrating with Playwright and use playwrightInterceptor()
    • Not using a test framework? The examples above use interceptor(), which patches fetch and XMLHttpRequest directly

    Set 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:

    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:

    • The Stoobly instance and interceptor are created once outside the describe block
    • withPage() 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 titles
    • Test titles are applied at request interception time

    By 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:

    • Intercepting requests from browser extensions
    • Intercepting requests from service workers
    • Intercepting requests across all pages in the same browser context

    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