window.Meticulous API Reference
Complete reference for the window.Meticulous JavaScript API available in your application.
Overview
The window.Meticulous object is exposed by the Meticulous recorder snippet and provides methods for:
- Detecting test mode
- Recording custom data
- Controlling replay timing
- Handling custom events
Availability: Only when Meticulous recorder is loaded.
TypeScript types: See TypeScript Types for full type definitions.
API Reference
isRunningAsTest
Type: boolean | undefined
Description: Indicates whether the app is running as a Meticulous test.
Values:
true: Running in test/replay modefalseorundefined: Running normally (production/development)
Example:
if (window.Meticulous?.isRunningAsTest) {
// Skip validation, use test data, etc.
console.log('Running as Meticulous test');
} else {
// Normal application logic
console.log('Running normally');
}
Agent swarm browsers, which record new sessions against your app, also set isRunningAsTest to true. Use isRunningInAgentSwarm to tell them apart from replays.
Common use cases:
- Skip form validation
- Bypass authentication
- Use deterministic values (timestamps, IDs)
- Show a fixed app version or commit SHA instead of the build's own
- Disable analytics/tracking
- Skip animations
isRunningInAgentSwarm
Type: boolean | undefined
Description: true when the app is being driven by a Meticulous Agent swarm browser. isRunningAsTest is also true there, so code that relaxes behavior for Meticulous tests applies to Agent swarm too; check this flag only when the two need to differ.
In Agent swarm the recorder is running, so window.Meticulous.record is available. window.Meticulous.replay is present but its controls (pause, resume, recordCustomSnapshot, listeners) are no-ops, and replay-only data such as sessionBeingReplayed, commitUnderTest and native is absent.
recordCustomValues()
Signature: recordCustomValues(values: Record<string, any>): void
Description: Store custom data during recording to be retrieved during replay.
Parameters:
values: Object with key-value pairs to store
Size limits:
- Development: 20MB total (set
data-is-production-environment="false") - Production: 1MB total
When to use:
- Store file upload contents
- Record feature flag values
- Save user context
- Preserve random/dynamic values
Example:
// During recording
const handleFileUpload = (file: File) => {
const reader = new FileReader();
reader.onload = (e) => {
const fileData = e.target?.result as string;
// Store for replay
if (window.Meticulous?.recordCustomValues) {
window.Meticulous.recordCustomValues({
uploadedFile: fileData,
fileName: file.name,
fileType: file.type,
});
}
processFile(fileData);
};
reader.readAsDataURL(file);
};
Multiple calls: Values are merged, not overwritten. Later calls add/update keys.
// First call
window.Meticulous.recordCustomValues({ key1: 'value1' });
// Second call - adds key2, keeps key1
window.Meticulous.recordCustomValues({ key2: 'value2' });
// Result: { key1: 'value1', key2: 'value2' }
getCustomValues()
Signature: getCustomValues(): Record<string, any> | undefined
Description: Retrieve custom values stored during recording.
Returns: Object with stored values, or undefined if none.
When to use:
- Restore file upload data during replay
- Get feature flag values
- Restore user context
Example:
// During replay
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
const stored = window.Meticulous.getCustomValues();
if (stored?.uploadedFile) {
// Restore file preview
setPreview(stored.uploadedFile);
}
if (stored?.featureFlags) {
// Apply feature flags
setFlags(stored.featureFlags);
}
}
}, []);
Complete pattern:
function useStoredValue(key: string) {
const [value, setValue] = useState(null);
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
const stored = window.Meticulous.getCustomValues();
if (stored?.[key]) {
setValue(stored[key]);
}
}
}, [key]);
return value;
}
// Usage
const userContext = useStoredValue('userContext');
pause()
Signature: pause(): void
Description: Pause replay until resume() is called.
When to use:
- Before async operations (data loading, image loading)
- Before third-party script initialization
- When content loads dynamically
Important: Always pair with resume(). Unpaired pause() will hang the test.
Example - Data Loading:
async function loadCriticalData() {
window.Meticulous?.pause?.();
try {
const data = await fetchDataFromAPI();
setData(data);
} finally {
window.Meticulous?.resume?.();
}
}
Example - Image Loading:
function LazyImage({ src }: { src: string }) {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (!loaded && window.Meticulous?.isRunningAsTest) {
window.Meticulous?.pause?.();
}
}, [loaded]);
const handleLoad = () => {
setLoaded(true);
window.Meticulous?.resume?.();
};
return <img src={src} onLoad={handleLoad} />;
}
resume()
Signature: resume(): void
Description: Resume replay after pause().
When to use: After the async operation started with pause() completes.
Example - Third-Party Script:
function loadGoogleMaps() {
return new Promise((resolve) => {
if (window.google?.maps) {
resolve(window.google.maps);
return;
}
window.Meticulous?.pause?.();
const script = document.createElement('script');
script.src = 'https://maps.googleapis.com/.../js?key=KEY';
script.onload = () => {
window.Meticulous?.resume?.();
resolve(window.google.maps);
};
script.onerror = () => {
window.Meticulous?.resume?.();
reject(new Error('Failed to load'));
};
document.head.appendChild(script);
});
}
recordCustomEvent()
Signature: recordCustomEvent(event: { type: string; payload?: any }): void
Description: Record a custom event with optional payload.
Parameters:
event.type: String identifying the event typeevent.payload: Optional data to store with event
When to use: Advanced scenarios requiring fine-grained replay control.
Example:
// Record event during session
function handlePaymentComplete(transaction: Transaction) {
if (window.Meticulous?.recordCustomEvent) {
window.Meticulous.recordCustomEvent({
type: 'PAYMENT_COMPLETE',
payload: {
transactionId: transaction.id,
amount: transaction.amount,
timestamp: Date.now(),
},
});
}
showSuccessMessage();
}
onReplayCustomEvent()
Signature: onReplayCustomEvent(handler: (event: CustomEvent) => void): void
Description: Register handler for custom events during replay.
Parameters:
handler: Function called when custom event is replayed
Example:
// Setup handler during component mount
useEffect(() => {
if (window.Meticulous?.onReplayCustomEvent) {
window.Meticulous.onReplayCustomEvent((event) => {
if (event.type === 'PAYMENT_COMPLETE') {
console.log('Replaying payment:', event.payload);
handlePaymentReplay(event.payload);
}
});
}
}, []);
Backend Detection
meticulous-is-test Header
Check for the meticulous-is-test header in server-side code:
Value: "1" when running as test
Security: For secure detection, set a custom secret header in project settings.
Next.js App Router
import { headers } from 'next/headers';
export async function isMeticulousTest(): Promise<boolean> {
const requestHeaders = await headers();
return requestHeaders.get('meticulous-is-test') === '1';
}
// Usage in Server Component
export default async function Page() {
const isTest = await isMeticulousTest();
if (isTest) {
// Skip auth, use test data, etc.
}
return <div>...</div>;
}
Next.js Pages Router
export const getServerSideProps = (context) => {
const { req } = context;
const isTest = req.headers['meticulous-is-test'] === '1';
return {
props: {
isRunningAsMeticulousTest: isTest,
},
};
};
function Page({ isRunningAsMeticulousTest }) {
if (isRunningAsMeticulousTest) {
// Test-specific rendering
}
return <div>...</div>;
}
Express.js
app.get('/api/data', (req, res) => {
const isTest = req.headers['meticulous-is-test'] === '1';
if (isTest) {
// Return test data
res.json({ data: 'test-data' });
} else {
// Return real data
res.json({ data: getRealData() });
}
});
Common Patterns
Pattern 1: Skip Validation
function submitForm(data: FormData) {
if (!window.Meticulous?.isRunningAsTest) {
// Only validate in normal mode
if (!data.email) {
throw new Error('Email required');
}
}
// Submit form
return api.post('/submit', data);
}
Pattern 2: Deterministic Values
function generateId(): string {
if (window.Meticulous?.isRunningAsTest) {
return 'test-id-12345';
}
return crypto.randomUUID();
}
function getCurrentTimestamp(): string {
if (window.Meticulous?.isRunningAsTest) {
return '2024-01-01T00:00:00Z';
}
return new Date().toISOString();
}
Pattern 3: Disable Third-Party Services
function initializeServices() {
if (!window.Meticulous?.isRunningAsTest) {
initializeAnalytics();
initializeSentry();
initializeIntercom();
}
}
Pattern 4: Feature Flags
function useFeatureFlag(flagName: string): boolean {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
const stored = window.Meticulous.getCustomValues();
if (stored?.featureFlags?.[flagName] !== undefined) {
setEnabled(stored.featureFlags[flagName]);
return;
}
}
// Normal flag fetching
fetchFlag(flagName).then(setEnabled);
}, [flagName]);
return enabled;
}
// Record flags during session
if (window.Meticulous?.recordCustomValues) {
window.Meticulous.recordCustomValues({
featureFlags: {
newCheckout: true,
experimentalUI: false,
},
});
}
Pattern 5: Conditional Rendering
function WelcomeBanner() {
if (window.Meticulous?.isRunningAsTest) {
// Don't show banner during tests
return null;
}
return <div>Welcome!</div>;
}
Integration Patterns
React Hook
function useMeticulous() {
return {
isTest: window.Meticulous?.isRunningAsTest ?? false,
recordValues: window.Meticulous?.recordCustomValues,
getValues: window.Meticulous?.getCustomValues,
pause: window.Meticulous?.pause,
resume: window.Meticulous?.resume,
};
}
// Usage
function MyComponent() {
const { isTest, recordValues } = useMeticulous();
if (isTest) {
// Test-specific logic
}
}
Context Provider
const MeticulousContext = createContext({
isTest: false,
recordValues: () => {},
getValues: () => ({}),
});
function MeticulousProvider({ children }) {
const value = {
isTest: window.Meticulous?.isRunningAsTest ?? false,
recordValues: window.Meticulous?.recordCustomValues?.bind(window.Meticulous) ?? (() => {}),
getValues: window.Meticulous?.getCustomValues?.bind(window.Meticulous) ?? (() => ({})),
};
return (
<MeticulousContext.Provider value={value}>
{children}
</MeticulousContext.Provider>
);
}
// Usage
const { isTest } = useContext(MeticulousContext);
Best Practices
1. Always Use Optional Chaining
// Good
window.Meticulous?.isRunningAsTest
// Bad - throws error if Meticulous not loaded
window.Meticulous.isRunningAsTest
2. Pair pause() with resume()
// Good - always resume
async function load() {
window.Meticulous?.pause?.();
try {
await fetchData();
} finally {
window.Meticulous?.resume?.();
}
}
// Bad - might not resume
async function load() {
window.Meticulous?.pause?.();
await fetchData();
window.Meticulous?.resume?.(); // Skipped if fetchData throws
}
3. Check isRunningAsTest Before Using Other Methods
// Good
if (window.Meticulous?.isRunningAsTest) {
const values = window.Meticulous.getCustomValues();
}
// Also good
const values = window.Meticulous?.getCustomValues?.();
4. Record Early, Retrieve Early
// Record as soon as data is available
useEffect(() => {
if (userData && window.Meticulous?.recordCustomValues) {
window.Meticulous.recordCustomValues({ user: userData });
}
}, [userData]);
// Retrieve during component initialization
useEffect(() => {
if (window.Meticulous?.isRunningAsTest) {
const stored = window.Meticulous.getCustomValues();
if (stored?.user) {
setUser(stored.user);
}
}
}, []); // Empty deps - run once
Troubleshooting
window.Meticulous is undefined
Cause: Recorder not loaded
Solutions:
- Verify recorder script tag is in HTML
- Check script loads before app code
- Check for CSP blocking
Custom values not available during replay
Cause: Values not recorded or size limit exceeded
Solutions:
- Check
recordCustomValueswas called during recording - Verify value size < 1MB (prod) or 20MB (dev)
- Check
data-is-production-environmentsetting
Pause/Resume hanging tests
Cause: resume() never called
Solutions:
- Use try/finally to ensure
resume()always runs - Check async callbacks complete
- Add timeout fallback
TypeScript Types
For full TypeScript type definitions:
interface Meticulous {
isRunningAsTest?: boolean;
recordCustomValues?: (values: Record<string, any>) => void;
getCustomValues?: () => Record<string, any> | undefined;
pause?: () => void;
resume?: () => void;
recordCustomEvent?: (event: { type: string; payload?: any }) => void;
onReplayCustomEvent?: (handler: (event: any) => void) => void;
}
declare global {
interface Window {
Meticulous?: Meticulous;
}
}
See TypeScript Types for importable types.
See Also
- Record Custom Values - Detailed custom values guide
- Custom Event API - Advanced event handling
- Handle File Uploads - File upload patterns