webcam-tester.js is a JavaScript library for:
- testing webcam and microphone functionality in web browsers
- priming browser/OS permissions (and default devices) before users reach your main application (read more about priming)
- diagnosing webcam & microphone issues
You can test it here.
- ✅ Tests for minimum requirements - Detects
getUserMedia(incl. legacy versions), secure contexts, Permissions Policy - 🎥 Camera Testing - Complete permission and device functionality checks with device selection
- 🎤 Microphone Testing - Independent microphone permission and device testing with device selection
- 📺 Resolution Testing - Tests multiple resolutions from 144p to 4K with frame rate detection
- 💡 Lighting Analysis - Analyzes camera brightness and provides recommendations
- 🔧 Other APIs - Tests MediaStream Recording API, MediaStream Image Capture API, Screen Capture API and Web Audio API
- 🎨 Dark Mode - Built-in light and dark themes
- ⚙️ Highly Configurable - Extensive customization options (see config options below)
- 📱 Device Enumeration - Lists all available audio/video input and output devices (reacts to
ondevicechange) - 🔒 Privacy-First - No data transmission or storage; Library runs locally in the browser
- 🚀 Easy Integration - Insert into any page using a single function call
- 👻 UI-less Mode - Run tests programmatically without UI
npm install @addpipe/webcam-testerimport { insertWebcamTestLibrary } from "@addpipe/webcam-tester";
// OR
const { insertWebcamTestLibrary } = require("@addpipe/webcam-tester");<!-- This element will be replaced by the library -->
<div id="webcam-tester-container"></div>const webcamTester = insertWebcamTestLibrary("webcam-tester-container");Adding the webcam tests to your web page is as simple as copying and pasting the code below in your page:
<!-- This element will be replaced by the library -->
<div id="webcam-tester-container"></div>
<!-- Minified version (recommended for production) -->
<script src="https://unpkg.com/@addpipe/webcam-tester@latest/dist/webcam-tester.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const webcamTester = insertWebcamTestLibrary("webcam-tester-container");
});
</script>That's it! The library will automatically replace the target element with a complete media testing interface.
You can also include the library’s source code directly in your page instead of loading it from a CDN. To do this, replace the line <script src="https://unpkg.com/@addpipe/webcam-tester@latest/dist/webcam-tester.min.js"></script> with the actual source code of the library inside a script tag.
For more advanced code examples, check out the Examples section.
The library accepts a configuration object with the following options:
const webcamTester = insertWebcamTestLibrary("webcam-tester-container", {
// UI Display Options
showResults: true, // Show test result logs (default: true)
showCameraPreview: true, // Show camera preview (default: true)
showRedoButtons: true, // Show individual redo buttons (default: true)
showLoadingText: true, // Show loading animations (default: true)
allowRestart: true, // Allow restarting entire test suite (default: true)
allowCameraSelection: true, // Allow camera device selection (default: true)
allowMicSelection: true, // Allow microphone device selection (default: true)
// Appearance
darkTheme: false, // Use dark theme (default: false)
title: "Webcam Tester", // Custom title (default: 'Webcam Tester')
// Execution Mode
uiLess: false, // Run without UI (default: false)
// Test Selection
tests: [
// Specific tests to run (default: all)
"getUserMedia",
"secureContext",
"permissionsPolicy",
"cameraPermissions",
"micPermissions",
"devices",
"capture",
"resolutions",
"lighting",
"otherApis",
],
// Event Callbacks
callbacks: {
onTestStart: function () {
console.log("Testing started");
},
onTestComplete: function (result) {
console.log("Test completed:", result);
},
onAllTestsComplete: function (allResults) {
console.log("All tests finished:", allResults);
},
onError: function (testName, error) {
console.error("Test error:", testName, error);
},
},
});What does the library test?
- Verifies if the browser supports the getUserMedia API
- Checks for legacy implementations
- Result: Success if modern API available, warning for legacy, error if unsupported
- Ensures the page is running in a secure context (HTTPS, localhost, file:// etc.)
- Result: Success if secure, error if not secure
- Verifies that Permissions Policy (formerly Feature Policy) allows camera and microphone access
- Expandable Info: Shows detailed policy status for each feature with explanations
- Result: Success if all features allowed, warning if some blocked, info if API not supported
Why this matters: Even if a user grants camera/microphone permissions, the Permissions Policy can block access at the browser level. This is common in iframes or when restrictive HTTP headers are set.
- Requests camera permissions from the user
- Allows selection of specific camera device (if
allowCameraSelection: true); on Chrome and Firefox, the selection made in the library UI has higher priority - Sets up the camera preview if successful
- Result: Success if granted, error with specific reason if denied
- Requests microphone permissions from the user
- Allows selection of specific microphone device (if
allowMicSelection: true); on Chrome and Firefox, the selection made in the library UI has higher priority - Works independently from camera permissions
- Result: Success if granted, error with specific reason if denied
- Lists all available audio inputs, video inputs, and audio outputs
- Shows which devices are currently selected
- Expandable Info: Shows detailed device lists by category with selection indicators
- Result: Success with device count
- Verifies is active media streams or tracks are working correctly
- Displays current capture resolution for video
- Shows status for both audio and video tracks
- Result: Success with resolution info, warning if partial capture
- Tests 8 standard resolutions: 144p, 240p, 360p, 480p, 720p, 1080p, 1440p, 4K
- Measures frame rates for each supported resolution
- Expandable Info: Shows all tested resolutions with status and frame rates
- Result: Success with supported count and average FPS
- Analyzes camera brightness using pixel data analysis
- Provides recommendations for optimal lighting
- Expandable Info: Shows brightness scale (0-255) with explanations
- Result: Success/warning based on lighting conditions with brightness value
- Tests availability of other web APIs:
- MediaStream Recording API (for recording)
- MediaStream Image Capture API (for photo capture)
- Screen Capture API (for screen sharing)
- Web Audio API (for audio processing)
- Expandable Info: Shows detailed status for each capability
- Result: Success if capabilities available, warning if limited
Once initialized, the library instance provides these methods:
const webcamTester = insertWebcamTestLibrary("webcam-tester-container");
// Start tests programmatically (useful in UI-less mode)
await webcamTester.start();
// Get all test results
const results = webcamTester.getTestResults();
console.log(results);
// Returns: { testId: { id, icon, message, type, details, timestamp, deviceId, deviceLabel }, ... }
// Check if tests are currently running
const isRunning = webcamTester.isRunning();
console.log("Tests running:", isRunning);
// Get the current media stream
const stream = webcamTester.getCurrentStream();
if (stream) {
// Use the stream for other purposes
console.log("Stream available:", stream);
}
// Get selected camera information
const cameraInfo = webcamTester.getSelectedCameraInfo();
console.log("Camera:", cameraInfo.deviceLabel, cameraInfo.deviceId);
// Get selected microphone information
const micInfo = webcamTester.getSelectedMicrophoneInfo();
console.log("Microphone:", micInfo.deviceLabel, micInfo.deviceId);
// Clean up resources and remove from DOM
webcamTester.destroy();Fired when the user clicks "Start Test" or when webcamTester.start() is called programmatically.
callbacks: {
onTestStart: function() {
console.log('User started testing process');
}
}Fired after each individual test completes. Receives a result object:
callbacks: {
onTestComplete: function(result) {
console.log(`Test ${result.id} completed:`, result.type);
// result = {
// id, icon, message, type, details,
// timestamp, deviceId, deviceLabel
// }
}
}Fired when all tests finish. Receives complete results object:
callbacks: {
onAllTestsComplete: function(allResults) {
console.log('Testing complete. Results:', allResults);
// Process final results
const failedTests = Object.values(allResults)
.filter(test => test.type === 'error');
console.log('Failed tests:', failedTests.length);
}
}Fired when a test encounters an error:
callbacks: {
onError: function(testName, error) {
console.error(`Error in ${testName}:`, error);
// Handle specific test failures
}
}import { insertWebcamTestLibrary } from "@addpipe/webcam-tester";
// Simple initialization with defaults
const webcamTester = insertWebcamTestLibrary("webcam-tester-container");<div id="webcam-tester-container"></div><script src="https://unpkg.com/@addpipe/webcam-tester@latest/dist/webcam-tester.min.js"></script>
<script>
// Simple initialization with defaults
const webcamTester = insertWebcamTestLibrary('webcam-tester-container');
</script>// Customized for a specific use case
const webcamTester = insertWebcamTestLibrary("webcam-tester-container", {
darkTheme: true,
title: "Camera & Mic Check",
tests: ["cameraPermissions", "micPermissions", "devices", "resolutions"],
callbacks: {
onAllTestsComplete: function (results) {
const cameraOk = results.cameraPermissions?.type === "success";
const micOk = results.micPermissions?.type === "success";
if (cameraOk && micOk) {
// Proceed with main application
window.location.href = "/start-recording";
} else {
// Show help message
alert("Camera and microphone access is required for recordings");
}
},
},
});// Run tests without any UI
const webcamTester = insertWebcamTestLibrary("webcam-tester-container", {
uiLess: true,
allowCameraSelection: false, // Skip device selection UI
allowMicSelection: false,
tests: ["getUserMedia", "secureContext", "cameraPermissions", "micPermissions"],
callbacks: {
onAllTestsComplete: function (results) {
console.log("uiLess test results:", results);
// Process results programmatically
if (results.cameraPermissions?.result && results.micPermissions?.result) {
startRecording();
} else {
showPermissionError();
}
},
},
});
// Start tests programmatically
await webcamTester.start();
// Get results at any time
const currentResults = webcamTester.getTestResults();import { useEffect, useState } from "react";
import { insertWebcamTestLibrary } from "@addpipe/webcam-tester";
function MediaTester() {
const [testResults, setTestResults] = useState(null);
const [testingComplete, setTestingComplete] = useState(false);
useEffect(() => {
const webcamTester = insertWebcamTestLibrary("webcam-tester-container", {
callbacks: {
onAllTestsComplete: (results) => {
setTestResults(results);
setTestingComplete(true);
},
},
});
return () => webcamTester.destroy(); // Cleanup on unmount
}, []);
return (
<div>
<div id="webcam-tester-container"></div>
{testingComplete && (
<div>
<h3>Test Results:</h3>
<pre>{JSON.stringify(testResults, null, 2)}</pre>
</div>
)}
</div>
);
}<template>
<div id="webcam-tester-container"></div>
</template>
<script>
import { onMounted, onUnmounted } from 'vue';
import { insertWebcamTestLibrary } from '@addpipe/webcam-tester';
export default {
name: 'MediaTester',
setup() {
let webcamTester = null;
onMounted(() => {
webcamTester = insertWebcamTestLibrary('webcam-tester-container', {
callbacks: {
onAllTestsComplete: (results) => {
console.log('Tests complete:', results);
}
}
});
});
onUnmounted(() => {
if (webcamTester) {
webcamTester.destroy();
}
});
}
};
</script>In this example, the library first confirms that camera and microphone permissions have been granted. It then redirects the user to a dedicated video recording page, ensuring that all required permissions are in place before starting the recording. The selected devices ID's are also stored within the local storage in this example.
// Use before main video calling interface
const webcamTester = insertWebcamTestLibrary("webcam-tester-container", {
tests: ["cameraPermissions", "micPermissions"],
allowCameraSelection: true,
allowMicSelection: true,
callbacks: {
onAllTestsComplete: function (results) {
const cameraOk = results.cameraPermissions?.type === "success";
const micOk = results.micPermissions?.type === "success";
if (cameraOk && micOk) {
// Get selected devices
const camera = webcamTester.getSelectedCameraInfo();
const mic = webcamTester.getSelectedMicrophoneInfo();
// Store preferences
localStorage.setItem("preferredCamera", camera.deviceId);
localStorage.setItem("preferredMic", mic.deviceId);
// Redirect to main app
window.location.href = "/record-video";
}
},
},
});In these examples, the library will only test the camera or mic permissions, not both.
// Test only camera
const cameraTester = insertWebcamTestLibrary("webcam-tester-container", {
tests: ["cameraPermissions", "resolutions", "lighting"],
allowMicSelection: false,
});
// Test only microphone
const micTester = insertWebcamTestLibrary("webcam-tester-container", {
tests: ["micPermissions", "devices"],
allowCameraSelection: false,
showCameraPreview: false,
});- On Chrome on macOS, it primes user permissions and the default devices (at the domain level; closing all tabs with said domain resets the permission)
- On Firefox on macOS, it primes user permissions and the default device (at the browser tab x device level; refreshing the tab does not reset permissions)
- On Safari on macOS, it primes user permission at the tab level (refreshing the tab resets the permission)
Priming works differently when the library is tested through file://.
The library supports device selection for both cameras and microphones. It’s especially handy on Safari which has no device selection UI. On Chrome and Firefox, because they offer their own device selector dialog, we prioritize the device chosen through the library (we use the exact keyword when requesting device access).
- Automatic Detection: If only one device is available, it's selected automatically
- Selection UI: If multiple devices exist, users can choose from a list. On browsers which have their own device selector (Chrome, Firefox, etc.) the device selected through the
webcam-tester.jsUI has higher priority (we use theexactkeyword) - Skip Selection: Set
allowCameraSelection: falseorallowMicSelection: falseto skip - UI-less Mode: In UI-less mode, the library automatically uses the media device selected by the user through the browser’s permission prompt. If no device selector is available, it defaults to the one provided by
getUserMedia
Device selection flow:
- Camera selection (if enabled and multiple cameras exist)
- Microphone selection (if enabled and multiple microphones exist)
- Tests execution
- No data transmission - All processing happens locally
- No data storage - Results are only kept in memory
- Stream cleanup - Automatically stops camera/microphone when removed using the
destroy()method
"getUserMedia not supported"
- Use HTTPS (not HTTP)
- Update to a modern browser
"Permission denied"
- User must click "Allow" in browser prompt
- Check browser settings for camera/microphone permissions
- Ensure no other application is using the devices
"No devices found"
- Check physical camera/microphone connections
- Verify devices work in other applications
- Check browser device permissions in settings
Camera preview shows but tests fail
- May indicate browser or hardware limitations
- Check console for specific error messages
Device selection not showing
- Check that
allowCameraSelectionorallowMicSelectionistrue - Ensure multiple devices are actually available
- Verify browser has permission to enumerate devices
Preview detailed logging by opening browser developer tools:
const webcamTester = insertWebcamTestLibrary("webcam-tester-container", {
callbacks: {
onError: (testName, error) => console.error(testName, error),
onTestComplete: (result) => console.log(result),
onAllTestsComplete: (results) => console.table(results),
},
});TypeScript definitions are included in the package:
import { insertWebcamTestLibrary, WebcamDeviceTester, TestResult } from "@addpipe/webcam-tester";
const webcamTester: WebcamDeviceTester = insertWebcamTestLibrary("webcam-tester-container", {
uiLess: true,
callbacks: {
onAllTestsComplete: (results: Record<string, TestResult>) => {
console.log(results);
},
},
});This library is designed to be extensible. To add new webcam & microphone tests:
- Add test configuration to the
testsarray - Implement test method following the pattern
testYourTestName() - Add test to the
testMapinrunAllTests() - Update documentation
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
See LICENSE for full details or visit https://www.gnu.org/licenses/agpl-3.0.html
- Added Permissions Policy test to check if camera and microphone access is permitted on the page. Permissions Policy is only supported by some browsers
- Fixed a bug where sometimes in React the target element was not found
- Initial release
- Core testing functionality
- Configuration options
- API methods and callbacks
Built with ❤️ by the addpipe.com team
