{"id":15966,"library":"appium-test-support","title":"Appium Test Support Utilities","description":"`appium-test-support` is a collection of JavaScript test utilities designed for internal use across various Appium packages. It provides common testing patterns such as environment variable stubbing (`stubEnv`), log output capturing and manipulation (`stubLog`), and advanced Sinon-based testing with sandboxes and mocks (`withSandbox`, `withMocks`, `fakeTime`). The current stable version is 1.3.3. As an internal utility library, its release cadence is tied to the broader Appium project's needs and updates, typically seeing updates in line with major Appium releases or when internal testing needs evolve. It differentiates itself by offering pre-packaged solutions for common Appium testing scenarios, including specific support for Android emulator setup on Travis CI, streamlining testing workflows within the Appium ecosystem and reducing boilerplate in Appium's own tests.","status":"active","version":"1.3.3","language":"javascript","source_language":"en","source_url":"https://github.com/appium/appium-test-support","tags":["javascript","appium"],"install":[{"cmd":"npm install appium-test-support","lang":"bash","label":"npm"},{"cmd":"yarn add appium-test-support","lang":"bash","label":"yarn"},{"cmd":"pnpm add appium-test-support","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"Primarily used in modern Node.js testing environments, assumes an ESM setup for direct imports.","wrong":"const stubEnv = require('appium-test-support').stubEnv;","symbol":"stubEnv","correct":"import { stubEnv } from 'appium-test-support';"},{"note":"This is a named export; direct access via a submodule path or a default import is incorrect.","wrong":"import stubLog from 'appium-test-support/stubLog';","symbol":"stubLog","correct":"import { stubLog } from 'appium-test-support';"},{"note":"Designed to integrate with Sinon.js for comprehensive mocking and stubbing within test suites. While CJS might work, ESM is the idiomatic usage shown.","wrong":"const { withSandbox } = require('appium-test-support');","symbol":"withSandbox","correct":"import { withSandbox } from 'appium-test-support';"},{"note":"Requires a Sinon sandbox instance for proper time manipulation and is a named export.","wrong":"import * as fakeTime from 'appium-test-support/fakeTime';","symbol":"fakeTime","correct":"import { fakeTime } from 'appium-test-support';"}],"quickstart":{"code":"import { fakeTime } from 'appium-test-support';\nimport sinon from 'sinon'; // Requires 'sinon' as a dev dependency\nimport { expect } from 'chai'; // Requires 'chai' for assertions\n\n// Simplified Promise-like object for demonstration if Bluebird isn't available\nclass CustomPromise {\n  constructor(executor) {\n    this.resolve = null;\n    this.reject = null;\n    const promise = new Promise((res, rej) => {\n      this.resolve = res;\n      this.reject = rej;\n    });\n    executor(this.resolve, this.reject);\n    return promise;\n  }\n}\n\nfunction doSomethingThatTakesTime() {\n  return new CustomPromise((resolve) => {\n    let ret = '';\n    function appendOneByOne () {\n      if(ret.length >= 10) {\n        return resolve(ret);\n      }\n      setTimeout(() => {\n        ret = ret + ret.length;\n        appendOneByOne();\n      }, 1000);\n    }\n    appendOneByOne();\n  });\n}\n\ndescribe('fakeTime demonstration', () => {\n  let sandbox: sinon.SinonSandbox;\n\n  beforeEach(() => {\n    sandbox = sinon.createSandbox();\n  });\n\n  afterEach(() => {\n    sandbox.restore();\n  });\n\n  it('should fast-forward time to test async operations', async () => {\n    const timeLord = fakeTime(sandbox);\n    const p = doSomethingThatTakesTime();\n    // Simulate 60 intervals of 200ms each, effectively advancing 12000ms (12 seconds)\n    // This allows the `doSomethingThatTakesTime` function to complete its 10 * 1000ms operations.\n    timeLord.speedup(200, 60);\n    expect(await p).to.equal('0123456789');\n  });\n});\n","lang":"typescript","description":"Demonstrates how to use `fakeTime` with a Sinon.js sandbox to control and fast-forward time in asynchronous tests, verifying the outcome of time-dependent operations. This setup requires `sinon` and a test runner like `mocha` with `chai` for assertions."},"warnings":[{"fix":"Review `sinon.js` migration guides for specific API changes and update your test code accordingly. Pin `sinon.js` version in your `package.json` if conflicts arise to ensure consistent behavior.","message":"Upgrades to underlying testing libraries like Sinon.js (if they are direct dependencies of `appium-test-support` or peer dependencies of your project) can introduce breaking changes to how stubs, mocks, and fake timers behave. Always consult the release notes of `sinon` when upgrading your testing dependencies.","severity":"breaking","affected_versions":">=1.0.0"},{"fix":"Always ensure `sandbox.restore()` is explicitly called in an `afterEach` hook or equivalent cleanup function provided by your test runner (e.g., `afterEach(() => sandbox.restore());`).","message":"Failure to properly manage Sinon sandboxes (e.g., calling `sandbox.restore()` in `afterEach`) can lead to test pollution, where stubs and mocks from one test leak into subsequent tests, causing unpredictable and flaky failures. This applies to `withSandbox`, `withMocks`, and `fakeTime`.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Always assume `stubEnv` provides local, test-scoped environment variable manipulation. For complex scenarios or interactions with external processes, consider explicit environment setup/teardown in hooks.","message":"`stubEnv` only affects `process.env` within the current test context and ensures it's restored after the test. Misunderstanding its scope can lead to confusion if expecting global or persistent changes, or if used in an environment where `process.env` might be mutated by other means outside the library's control.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Verify the `stripColors` behavior with your specific logging library. If not working as expected, manually strip non-ANSI color codes or customize the `stubLog` output processing further before asserting.","message":"The `stripColors` option in `stubLog` specifically targets common ANSI color codes. If your logging library uses non-standard color representations or custom formatting, `stripColors: true` might not produce the expected plain text output, potentially leaving unexpected characters in your log assertions.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Ensure the object being mocked is explicitly included in the `mocks` configuration object passed to `withMocks` or `withSandbox` and accessed via the provided `mocks` or `S.mocks` parameter, e.g., `withMocks({ myApi }, (mocks) => { mocks.myApi.expects(...) });`.","cause":"Attempting to call `.expects()` on a mock object that has not been correctly initialized or passed to `withMocks` or `withSandbox`.","error":"TypeError: Cannot read properties of undefined (reading 'expects')"},{"fix":"Ensure `sinon.createSandbox()` is called in a `beforeEach` hook and the resulting sandbox instance is passed to `fakeTime`. Also, always call `sandbox.restore()` in an `afterEach` hook to clean up.","cause":"This error typically occurs when `fakeTime` or other sandbox-dependent utilities are used without an active Sinon sandbox instance, or if `sinon.createSandbox()` was not called prior to their use.","error":"Error: Sandbox not found"},{"fix":"Verify that no other testing utilities or libraries are conflictingly stubbing `process.env` or `console` within the same test context. Ensure `stubEnv` is used within a clean context where its internal restoration mechanism can operate without interference.","cause":"A common issue when stubbing global objects like `console` or `process.env` (which `stubEnv` might interact with implicitly) without proper restoration, especially if multiple testing libraries or stubs conflict.","error":"Error: 'console.error' was replaced, but unable to restore."}],"ecosystem":"npm"}