Show HN: Droideer – Puppeteer-like API for Android app automation and scraping
4 hours ago
2
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:
🧪 End-to-end testing of Android applications
📊 Data scraping from mobile apps and services
🤖 Workflow automation and repetitive task automation
🔍 App behavior analysis and reverse engineering
📱 Cross-platform testing across different Android devices
🔧 Android DevTools Companion
Note: For a visual UI inspector and debugging experience similar to Chrome DevTools, check out our companion repository:
Droideer Inspection Tools 🛠️
🎯 Puppeteer-like API - Familiar syntax for web developers
📊 Structured Data Extraction - Extract listings, properties, products, and more
🎯 Element-Specific Targeting - Precise data extraction using resource IDs and selectors
💾 Export Support - Save scraped data in JSON, CSV, or custom formats
ADB (Android Debug Bridge) installed and in your PATH
Android device with USB debugging enabled or an emulator
Node.js 16 or higher
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 deviceconstdevice=awaitDroideer.connect();constpage=awaitdevice.launch('com.android.settings');// Interact with UIawaitpage.click({text: 'Network & internet'});awaitpage.waitForSelector({text: 'Wi-Fi'});awaitpage.screenshot('wifi-settings.png');awaitdevice.disconnect();
import{Droideer}from'droideer';constdevice=awaitDroideer.connect();constpage=awaitdevice.launch('com.example.marketplace');// Navigate to searchawaitpage.click({resourceId: 'search_button'});awaitpage.type({resourceId: 'search_input'},'smartphones');awaitpage.pressKey(66);// Enter// Scrape product listings with infinite scrollconstproducts=[];letscrollCount=0;constmaxScrolls=20;while(scrollCount<maxScrolls){// Extract current page productsconstproductElements=awaitpage.$$({resourceId: 'product_item'});for(constelementofproductElements){consttitle=awaitelement.getText();constprice=awaitpage.getText({resourceId: 'product_price'});products.push({ title, price });}// Scroll for more productsawaitpage.scroll('down');awaitpage.waitForTimeout(2000);scrollCount++;// Check if we've reached the endconstnoMoreItems=awaitpage.$({text: 'No more items'});if(noMoreItems)break;}console.log(`Scraped ${products.length} products`);
import{Droideer}from'droideer';constdevice=awaitDroideer.connect();// Monitor network activity during app usageconstnetworkResults=awaitdevice.networkMonitor.monitorAction(async()=>{constpage=awaitdevice.launch('com.booking');awaitpage.click({text: 'Search'});awaitpage.type({resourceId: 'destination'},'Paris');awaitpage.click({text: 'Search hotels'});// Wait for API calls to completeawaitpage.waitForTimeout(5000);},{targetDomains: ['booking.com','api.booking'],// Only monitor Booking APIscaptureResponses: 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 deviceconstdevice=awaitDroideer.connect();// Launch an appconstpage=awaitdevice.launch('com.android.settings');// Take a screenshotawaitdevice.screenshot('device.png');// Press hardware buttonsawaitdevice.back();awaitdevice.home();// Disconnect when doneawaitdevice.disconnect();
The Page class represents the current UI view of an Android app.
// Find elementsconstelement=awaitpage.$({text: 'Settings'});constelements=awaitpage.$$({className: 'android.widget.Button'});// Convenience methodsconstbutton=awaitpage.findByResourceId('submit_button');consttextElement=awaitpage.findByText('Welcome');// Interact with UIawaitpage.click({text: 'Next'});awaitpage.type({resourceId: 'username_field'},'john_doe');awaitpage.scroll('down',500);// Wait for elements or conditionsawaitpage.waitForSelector({text: 'Welcome'});awaitpage.waitForNavigation();
The AndroidElement class represents a UI element on the screen.
// Get element propertiesconsttext=element.text;constresourceId=element.resourceId;constisEnabled=element.isEnabled;// Interact with elementawaitelement.click();awaitelement.type('Hello world');awaitelement.longPress();awaitelement.swipeLeft();
Droideer supports multiple selector strategies for finding elements:
// By textawaitpage.$({text: 'Login'});// By partial textawaitpage.$({contains: 'Log'});// By resource IDawaitpage.$({resourceId: 'com.example.app:id/username'});// By class nameawaitpage.$({className: 'android.widget.EditText'});// By content descriptionawaitpage.$({contentDesc: 'Profile picture'});// Combined selectorsawaitpage.$({className: 'android.widget.Button',text: 'Login',clickable: true});// XPath-like selectorsawaitpage.$x('//android.widget.Button[@text="Submit"]');
// Start monitoringawaitdevice.networkMonitor.startMonitoring({targetDomains: ['api.example.com'],captureResponses: true});// Perform actions...// Stop and get resultsconstresults=awaitdevice.networkMonitor.stopMonitoring();
import{test,expect}from'@playwright/test';import{Droideer}from'droideer';test('user can complete purchase flow',async()=>{constdevice=awaitDroideer.connect();constpage=awaitdevice.launch('com.example.shop');// Test complete user journeyawaitpage.click({text: 'Shop Now'});awaitpage.type({resourceId: 'search'},'running shoes');awaitpage.click({text: 'Search'});constfirstProduct=awaitpage.$({resourceId: 'product_item'});awaitfirstProduct.click();awaitpage.click({text: 'Add to Cart'});awaitpage.click({resourceId: 'cart_button'});awaitpage.click({text: 'Checkout'});constconfirmationText=awaitpage.waitForSelector({text: 'Order confirmed'});expect(confirmationText).toBeTruthy();});
// Scrape real estate listingsconstdevice=awaitDroideer.connect();constpage=awaitdevice.launch('com.idealista.android');constlistings=[];lethasMorePages=true;while(hasMorePages){constpropertyElements=awaitpage.$$({resourceId: 'property_card'});for(constpropertyofpropertyElements){consttitle=awaitproperty.getText();constprice=awaitpage.getText({resourceId: 'property_price'});constlocation=awaitpage.getText({resourceId: 'property_location'});listings.push({ title, price, location });}// Try to go to next pageconstnextButton=awaitpage.$({text: 'Next'});if(nextButton){awaitnextButton.click();awaitpage.waitForTimeout(3000);}else{hasMorePages=false;}}console.log(`Scraped ${listings.length} property listings`);
3. API Reverse Engineering ( Work in Progress )
// Monitor network traffic to understand app APIsconstdevice=awaitDroideer.connect();constapiAnalysis=awaitdevice.networkMonitor.monitorAction(async()=>{constpage=awaitdevice.launch('com.airbnb.android');awaitpage.type({resourceId: 'search_location'},'New York');awaitpage.click({text: 'Search'});awaitpage.waitForTimeout(5000);// Scroll to trigger pagination APIsfor(leti=0;i<5;i++){awaitpage.scroll('down');awaitpage.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.