
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
The Puppeteer for Android - Control Android devices with familiar web automation syntax
Puppeteer-like API for Android automation - Control Android devices with familiar web automation syntax for testing, scraping, and automation
⚠️ Work In Progress: This repository is currently under active development. Features and APIs may change. Emulator support is in testing phase.
Droideer brings the beloved Puppeteer API to Android automation. Whether you're testing apps, scraping mobile data, or automating workflows, if you've ever automated web browsers, you'll feel right at home automating Android apps!
Perfect for:
Note: For a visual UI inspector and debugging experience similar to Chrome DevTools, check out our companion repository: Droideer DevTools 🛠️
import { Droideer } from 'droideer';
const device = await Droideer.connect();
const page = await device.launch('com.instagram.android');
await page.type({ resourceId: 'login_username' }, 'john_doe');
await page.type({ resourceId: 'password' }, 'secret123');
await page.click({ text: 'Log In' });
await page.waitForSelector({ text: 'Home' });
await page.screenshot('logged-in.png');
npm install droideer
Note: Emulator support is currently in testing phase. Physical devices are recommended for production use.
Some Examples can be checked in the Examples folder.
import { Droideer } from 'droideer';
// Connect to device
const device = await Droideer.connect();
const page = await device.launch('com.android.settings');
// Interact with UI
await page.click({ text: 'Network & internet' });
await page.waitForSelector({ text: 'Wi-Fi' });
await page.screenshot('wifi-settings.png');
await device.disconnect();
import { Droideer } from 'droideer';
const device = await Droideer.connect();
const page = await device.launch('com.example.marketplace');
// Navigate to search
await page.click({ resourceId: 'search_button' });
await page.type({ resourceId: 'search_input' }, 'smartphones');
await page.pressKey(66); // Enter
// Scrape product listings with infinite scroll
const products = [];
let scrollCount = 0;
const maxScrolls = 20;
while (scrollCount < maxScrolls) {
// Extract current page products
const productElements = await page.$$({ resourceId: 'product_item' });
for (const element of productElements) {
const title = await element.getText();
const price = await page.getText({ resourceId: 'product_price' });
products.push({ title, price });
}
// Scroll for more products
await page.scroll('down');
await page.waitForTimeout(2000);
scrollCount++;
// Check if we've reached the end
const noMoreItems = await page.$({ text: 'No more items' });
if (noMoreItems) break;
}
console.log(`Scraped ${products.length} products`);
import { Droideer } from 'droideer';
const device = await Droideer.connect();
// Monitor network activity during app usage
const networkResults = await device.networkMonitor.monitorAction(async () => {
const page = await device.launch('com.booking');
await page.click({ text: 'Search' });
await page.type({ resourceId: 'destination' }, 'Paris');
await page.click({ text: 'Search hotels' });
// Wait for API calls to complete
await page.waitForTimeout(5000);
}, {
targetDomains: ['booking.com', 'api.booking'], // Only monitor Booking APIs
captureResponses: true
});
console.log(`Captured ${networkResults.summary.totalRequests} network requests`);
console.log('API Endpoints:', networkResults.data.apiEndpoints);
The AndroidDevice class represents a connected Android device.
// Connect to a device
const device = await Droideer.connect();
// Launch an app
const page = await device.launch('com.android.settings');
// Take a screenshot
await device.screenshot('device.png');
// Press hardware buttons
await device.back();
await device.home();
// Disconnect when done
await device.disconnect();
The Page class represents the current UI view of an Android app.
// Find elements
const element = await page.$({ text: 'Settings' });
const elements = await page.$$({ className: 'android.widget.Button' });
// Convenience methods
const button = await page.findByResourceId('submit_button');
const textElement = await page.findByText('Welcome');
// Interact with UI
await page.click({ text: 'Next' });
await page.type({ resourceId: 'username_field' }, 'john_doe');
await page.scroll('down', 500);
// Wait for elements or conditions
await page.waitForSelector({ text: 'Welcome' });
await page.waitForNavigation();
The AndroidElement class represents a UI element on the screen.
// Get element properties
const text = element.text;
const resourceId = element.resourceId;
const isEnabled = element.isEnabled;
// Interact with element
await element.click();
await element.type('Hello world');
await element.longPress();
await element.swipeLeft();
Droideer supports multiple selector strategies for finding elements:
// By text
await page.$({ text: 'Login' });
// By partial text
await page.$({ contains: 'Log' });
// By resource ID
await page.$({ resourceId: 'com.example.app:id/username' });
// By class name
await page.$({ className: 'android.widget.EditText' });
// By content description
await page.$({ contentDesc: 'Profile picture' });
// Combined selectors
await page.$({
className: 'android.widget.Button',
text: 'Login',
clickable: true
});
// XPath-like selectors
await page.$x('//android.widget.Button[@text="Submit"]');
// Start monitoring
await device.networkMonitor.startMonitoring({
targetDomains: ['api.example.com'],
captureResponses: true
});
// Perform actions...
// Stop and get results
const results = await device.networkMonitor.stopMonitoring();
import { test, expect } from '@playwright/test';
import { Droideer } from 'droideer';
test('user can complete purchase flow', async () => {
const device = await Droideer.connect();
const page = await device.launch('com.example.shop');
// Test complete user journey
await page.click({ text: 'Shop Now' });
await page.type({ resourceId: 'search' }, 'running shoes');
await page.click({ text: 'Search' });
const firstProduct = await page.$({ resourceId: 'product_item' });
await firstProduct.click();
await page.click({ text: 'Add to Cart' });
await page.click({ resourceId: 'cart_button' });
await page.click({ text: 'Checkout' });
const confirmationText = await page.waitForSelector({ text: 'Order confirmed' });
expect(confirmationText).toBeTruthy();
});
// Scrape real estate listings
const device = await Droideer.connect();
const page = await device.launch('com.idealista.android');
const listings = [];
let hasMorePages = true;
while (hasMorePages) {
const propertyElements = await page.$$({ resourceId: 'property_card' });
for (const property of propertyElements) {
const title = await property.getText();
const price = await page.getText({ resourceId: 'property_price' });
const location = await page.getText({ resourceId: 'property_location' });
listings.push({ title, price, location });
}
// Try to go to next page
const nextButton = await page.$({ text: 'Next' });
if (nextButton) {
await nextButton.click();
await page.waitForTimeout(3000);
} else {
hasMorePages = false;
}
}
console.log(`Scraped ${listings.length} property listings`);
// Monitor network traffic to understand app APIs
const device = await Droideer.connect();
const apiAnalysis = await device.networkMonitor.monitorAction(async () => {
const page = await device.launch('com.airbnb.android');
await page.type({ resourceId: 'search_location' }, 'New York');
await page.click({ text: 'Search' });
await page.waitForTimeout(5000);
// Scroll to trigger pagination APIs
for (let i = 0; i < 5; i++) {
await page.scroll('down');
await page.waitForTimeout(2000);
}
}, {
targetDomains: ['airbnb.com'],
targetKeywords: ['api', 'search', 'listings'],
captureResponses: true
});
console.log('Discovered API endpoints:', apiAnalysis.data.apiEndpoints);
console.log('JSON responses:', apiAnalysis.data.jsonResponses.length);
Droideer is perfect for end-to-end testing of Android applications. See TESTING.md for detailed testing guidelines.
import { Droideer } from 'droideer';
import { test, expect } from 'your-test-framework';
test('should login successfully', async () => {
const device = await Droideer.connect();
const page = await device.launch('com.example.app');
await page.type({ resourceId: 'username' }, 'testuser');
await page.type({ resourceId: 'password' }, 'password123');
await page.click({ text: 'Login' });
const welcomeElement = await page.waitForSelector({ text: 'Welcome' });
expect(welcomeElement).not.toBeNull();
await device.disconnect();
});
This project is currently under active development. Here's what's working and what's planned:
Contributions are welcome! Please feel free to submit a Pull Request. See CONTRIBUTING.md for detailed guidelines.
git clone https://github.com/your-username/droideer.git
cd droideer
npm install
npm run build
npm test
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ for Android automation, testing, and data extraction
Star ⭐ this repo if you find it useful!
FAQs
The Puppeteer for Android - Control Android devices with familiar web automation syntax
We found that droideer demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.