Skip to content

Best Practices

Guidelines for building reliable and efficient browser automation with Browserman.

Account Management

Use Descriptive Names

Choose clear, meaningful names for your accounts:

Good:

personal-twitter
work-twitter-official
xueqiu-trading-main
eastmoney-analysis

Bad:

account1
test
a
twitter123

Organize Accounts

Use a consistent naming convention:

{purpose}-{platform}-{identifier}

Examples:
personal-twitter-main
work-xueqiu-primary
brand-twitter-official
testing-twitter-dev

Separate Environments

Use different accounts for different environments:

javascript
const ACCOUNTS = {
  development: 'dev-twitter',
  staging: 'staging-twitter',
  production: 'prod-twitter'
};

const accountName = ACCOUNTS[process.env.NODE_ENV];

Monitor Account Health

Regularly check account status:

javascript
async function checkAccountHealth() {
  const accounts = await client.accounts.list();

  const unhealthy = accounts.filter(acc => acc.status !== 'active');

  if (unhealthy.length > 0) {
    console.warn('Unhealthy accounts:', unhealthy);
    // Send alert
    await notifyTeam(unhealthy);
  }
}

// Run daily
cron.schedule('0 0 * * *', checkAccountHealth);

Rate Limiting

Respect Platform Limits

Each platform has different rate limits:

javascript
const RATE_LIMITS = {
  twitter: {
    tweets: { count: 300, window: 3 * 60 * 60 * 1000 }, // 3 hours
    likes: { count: 1000, window: 24 * 60 * 60 * 1000 } // 24 hours
  },
  xueqiu: {
    posts: { count: 50, window: 24 * 60 * 60 * 1000 }
  }
};

Implement Request Throttling

Use a queue to control request rate:

javascript
class RateLimiter {
  constructor(maxRequests, windowMs) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = [];
  }

  async acquire() {
    const now = Date.now();

    // Remove old requests outside window
    this.requests = this.requests.filter(
      time => now - time < this.windowMs
    );

    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = this.windowMs - (now - oldestRequest);
      await sleep(waitTime);
      return this.acquire();
    }

    this.requests.push(now);
  }
}

// Usage
const limiter = new RateLimiter(300, 3 * 60 * 60 * 1000);

for (const tweet of tweets) {
  await limiter.acquire();
  await postTweet(tweet);
}

Add Delays Between Requests

Mimic human behavior with random delays:

javascript
async function randomDelay(min = 1000, max = 5000) {
  const delay = Math.random() * (max - min) + min;
  await sleep(delay);
}

async function postMultipleTweets(tweets) {
  for (const tweet of tweets) {
    await postTweet(tweet);
    await randomDelay(2000, 5000); // 2-5 seconds
  }
}

Error Handling

Implement Retry Logic

Handle transient errors with exponential backoff:

javascript
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      const isLastAttempt = i === maxRetries - 1;

      if (isLastAttempt) {
        throw error;
      }

      // Check if error is retryable
      if (!isRetryableError(error)) {
        throw error;
      }

      // Exponential backoff: 1s, 2s, 4s, 8s...
      const waitTime = Math.pow(2, i) * 1000;
      console.log(`Retry ${i + 1}/${maxRetries} after ${waitTime}ms`);
      await sleep(waitTime);
    }
  }
}

function isRetryableError(error) {
  const retryableCodes = [
    'RATE_LIMITED',
    'TIMEOUT',
    'PLATFORM_ERROR',
    'CONNECTION_ERROR'
  ];

  return retryableCodes.includes(error.code);
}

// Usage
const result = await retryWithBackoff(async () => {
  return await client.twitter.createTweet({
    accountName: 'my-twitter',
    text: 'Hello World!'
  });
});

Handle Specific Errors

Different errors require different handling:

javascript
async function executeTaskWithHandling(task) {
  try {
    return await client.tasks.create(task);
  } catch (error) {
    switch (error.code) {
      case 'RATE_LIMITED':
        // Wait and retry
        await sleep(error.retryAfter * 1000);
        return executeTaskWithHandling(task);

      case 'ACCOUNT_NOT_FOUND':
        // Log and skip
        console.error(`Account not found: ${task.accountName}`);
        return null;

      case 'AUTHENTICATION_FAILED':
        // Reconnect account
        await reconnectAccount(task.accountName);
        return executeTaskWithHandling(task);

      case 'INVALID_PARAMETERS':
        // Log and don't retry
        console.error(`Invalid parameters:`, task.parameters);
        throw error;

      default:
        throw error;
    }
  }
}

Log Errors Properly

Include context in error logs:

javascript
try {
  await client.tasks.create(task);
} catch (error) {
  console.error('Task creation failed:', {
    error: error.message,
    code: error.code,
    task: {
      platform: task.platform,
      tool: task.tool,
      accountName: task.accountName
    },
    timestamp: new Date().toISOString()
  });
}

Task Management

Always Check Task Status

Don't assume tasks succeeded:

javascript
async function executeAndVerify(task) {
  // Create task
  const created = await client.tasks.create(task);

  // Wait for completion
  const result = await client.tasks.waitFor(created.taskId);

  // Verify success
  if (result.state !== 'completed') {
    throw new Error(`Task failed: ${result.error?.message}`);
  }

  return result.result;
}

Implement Polling Wisely

Use appropriate polling intervals:

javascript
async function pollTaskStatus(taskId, maxWaitTime = 300000) {
  const startTime = Date.now();
  let pollInterval = 5000; // Start with 5 seconds

  while (Date.now() - startTime < maxWaitTime) {
    const status = await client.tasks.get(taskId);

    if (['completed', 'failed', 'timeout', 'cancelled'].includes(status.state)) {
      return status;
    }

    await sleep(pollInterval);

    // Increase interval gradually
    pollInterval = Math.min(pollInterval * 1.2, 30000); // Max 30s
  }

  throw new Error('Polling timeout');
}

Use Webhooks for Long Tasks

For tasks that take a while, use webhooks instead of polling:

javascript
// Configure webhook
await client.webhooks.create({
  url: 'https://your-app.com/webhooks/browserman',
  events: ['task.completed', 'task.failed']
});

// Create task without waiting
const task = await client.tasks.create({...});

// Your webhook handler will be called when complete
app.post('/webhooks/browserman', (req, res) => {
  const { event, taskId, data } = req.body;

  if (event === 'task.completed') {
    handleTaskSuccess(taskId, data.result);
  } else if (event === 'task.failed') {
    handleTaskFailure(taskId, data.error);
  }

  res.status(200).send('OK');
});

Engine Selection

Choose the Right Engine

Use lite mode by default, full mode when needed:

javascript
function selectEngine(task) {
  // Use full mode for:
  // - Important operations
  // - When lite mode gets detected
  // - Sensitive accounts

  const useFullMode =
    task.priority === 'high' ||
    task.account.hasDetectionIssues ||
    task.operation === 'sensitive';

  return useFullMode ? 'full' : 'lite';
}

await client.tasks.create({
  platform: 'twitter',
  tool: 'createTweet',
  accountName: 'important-account',
  parameters: {
    text: 'Important announcement',
    preferredEngine: selectEngine(task)
  }
});

Monitor Engine Performance

Track which engine works best for your use case:

javascript
const engineStats = {
  lite: { success: 0, failure: 0 },
  full: { success: 0, failure: 0 }
};

async function executeWithTracking(task) {
  const engine = task.parameters.preferredEngine || 'lite';
  const startTime = Date.now();

  try {
    const result = await client.tasks.create(task);
    engineStats[engine].success++;

    console.log(`${engine} mode succeeded in ${Date.now() - startTime}ms`);
    return result;
  } catch (error) {
    engineStats[engine].failure++;

    console.error(`${engine} mode failed:`, error.message);

    // Try full mode if lite failed
    if (engine === 'lite') {
      console.log('Retrying with full mode...');
      task.parameters.preferredEngine = 'full';
      return executeWithTracking(task);
    }

    throw error;
  }
}

Security

Never Expose API Keys

javascript
// ❌ Bad
const API_KEY = 'sk_live_abc123';

// ✅ Good
const API_KEY = process.env.BROWSERMAN_API_KEY;
if (!API_KEY) {
  throw new Error('BROWSERMAN_API_KEY not set');
}

Validate Input

Always validate user input before creating tasks:

javascript
function validateTweet(text) {
  if (!text || typeof text !== 'string') {
    throw new Error('Tweet text is required');
  }

  if (text.length > 280) {
    throw new Error('Tweet too long (max 280 characters)');
  }

  if (text.length === 0) {
    throw new Error('Tweet cannot be empty');
  }

  return text.trim();
}

const tweet = validateTweet(userInput);
await postTweet(tweet);

Sanitize Content

Remove sensitive information before posting:

javascript
function sanitizeContent(text) {
  // Remove email addresses
  text = text.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[email]');

  // Remove phone numbers
  text = text.replace(/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, '[phone]');

  // Remove API keys
  text = text.replace(/sk_[a-zA-Z0-9]{32,}/g, '[redacted]');

  return text;
}

Performance

Batch Operations

Process multiple tasks efficiently:

javascript
async function batchPostTweets(tweets, accountName) {
  const taskPromises = tweets.map(async (tweet, index) => {
    // Stagger requests to avoid rate limiting
    await sleep(index * 2000);

    return client.tasks.create({
      platform: 'twitter',
      tool: 'createTweet',
      accountName,
      parameters: { text: tweet }
    });
  });

  // Wait for all tasks
  const results = await Promise.allSettled(taskPromises);

  // Check results
  const succeeded = results.filter(r => r.status === 'fulfilled');
  const failed = results.filter(r => r.status === 'rejected');

  console.log(`Posted ${succeeded.length}/${tweets.length} tweets`);

  if (failed.length > 0) {
    console.error(`Failed to post ${failed.length} tweets`);
  }

  return { succeeded, failed };
}

Cache Platform Schema

Cache the platform schema to avoid repeated requests:

javascript
let platformSchemaCache = null;
let cacheTime = null;
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours

async function getPlatformSchema() {
  const now = Date.now();

  if (platformSchemaCache && cacheTime && now - cacheTime < CACHE_TTL) {
    return platformSchemaCache;
  }

  platformSchemaCache = await client.platforms.getSchema();
  cacheTime = now;

  return platformSchemaCache;
}

Monitor Performance

Track and log performance metrics:

javascript
class PerformanceMonitor {
  constructor() {
    this.metrics = {
      taskCount: 0,
      successCount: 0,
      failureCount: 0,
      totalDuration: 0
    };
  }

  async trackTask(fn) {
    const startTime = Date.now();
    this.metrics.taskCount++;

    try {
      const result = await fn();
      this.metrics.successCount++;
      return result;
    } catch (error) {
      this.metrics.failureCount++;
      throw error;
    } finally {
      this.metrics.totalDuration += Date.now() - startTime;
    }
  }

  getStats() {
    return {
      ...this.metrics,
      successRate: this.metrics.successCount / this.metrics.taskCount,
      avgDuration: this.metrics.totalDuration / this.metrics.taskCount
    };
  }
}

const monitor = new PerformanceMonitor();

// Track tasks
await monitor.trackTask(() => postTweet('Hello!'));

// View stats
console.log(monitor.getStats());

Testing

Test in Development

Always test with development accounts first:

javascript
const isDev = process.env.NODE_ENV === 'development';
const accountName = isDev ? 'dev-twitter' : 'prod-twitter';

await client.tasks.create({
  platform: 'twitter',
  tool: 'createTweet',
  accountName,
  parameters: {
    text: isDev ? '[TEST] Hello World' : 'Hello World'
  }
});

Mock for Unit Tests

Mock the Browserman client in tests:

javascript
// __mocks__/browserman-client.js
export class BrowsermanClient {
  async tasks.create(task) {
    return {
      taskId: 'mock_task_123',
      state: 'pending'
    };
  }

  async tasks.get(taskId) {
    return {
      taskId,
      state: 'completed',
      result: { success: true }
    };
  }
}

Next Steps