TFLogin API Documentation

TFLogin API Documentation

This documentation describes the available API endpoints for the TFLogin application.

Quick Start 🚀

Here are some quick examples to get you started with the TFLogin API:

SET API KEY

cURL
Python
JavaScript
C#
curl "http://127.0.0.1:5000/set-api-key?key=point_xxx"
import requests

# Set API key
response = requests.get("http://127.0.0.1:5000/set-api-key?key=point_xxx")
print(response.json())
fetch('http://127.0.0.1:5000/set-api-key?key=point_xxx')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Set API key
        using var httpClient = new HttpClient();
        var response = await httpClient.GetAsync("http://127.0.0.1:5000/set-api-key?key=point_xxx");
        var content = await response.Content.ReadAsStringAsync();
        Console.WriteLine(content);
    }
}

Launch a browser with profile ID 1:

cURL
Python
JavaScript
C#
curl -X POST http://127.0.0.1:5000/run-trungfox \
-H "Content-Type: application/json" \
-d '{"ID_Folder": 1, "isServer": true}'
import requests
import json

# Launch browser with profile ID 1
response = requests.post(
    "http://127.0.0.1:5000/run-trungfox",
    json={"ID_Folder": 1, "isServer": True},
    headers={"Content-Type": "application/json"}
)
print(response.json())
fetch('http://127.0.0.1:5000/run-trungfox', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        ID_Folder: 1,
        isServer: true
    })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Launch browser with profile ID 1
        using var httpClient = new HttpClient();
        
        var request = new
        {
            ID_Folder = 1,
            isServer = true
        };
        
        var jsonContent = JsonSerializer.Serialize(request);
        var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
        
        var response = await httpClient.PostAsync("http://127.0.0.1:5000/run-trungfox", content);
        var responseContent = await response.Content.ReadAsStringAsync();
        Console.WriteLine(responseContent);
    }
}

Check available profiles:

cURL
Python
JavaScript
C#
curl http://127.0.0.1:5000/list-ID_Folder
import requests

# Check available profiles
response = requests.get("http://127.0.0.1:5000/list-ID_Folder")
profiles = response.json()
print(profiles)
fetch('http://127.0.0.1:5000/list-ID_Folder')
  .then(response => response.json())
  .then(profiles => console.log(profiles))
  .catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Check available profiles
        using var httpClient = new HttpClient();
        var response = await httpClient.GetAsync("http://127.0.0.1:5000/list-ID_Folder");
        var content = await response.Content.ReadAsStringAsync();
        
        // Parse and display the profile IDs
        var profiles = JsonSerializer.Deserialize(content);
        Console.WriteLine("Available profiles:");
        foreach (var profileId in profiles)
        {
            Console.WriteLine($"- {profileId}");
        }
    }
}

Kill a running browser:

cURL
Python
JavaScript
C#
curl http://127.0.0.1:5000/kill-process?ID_Folder=1
import requests

# Kill a running browser
response = requests.get("http://127.0.0.1:5000/kill-process?ID_Folder=1")
print(response.json())
fetch('http://127.0.0.1:5000/kill-process?ID_Folder=1')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Kill a running browser
        using var httpClient = new HttpClient();
        var response = await httpClient.GetAsync("http://127.0.0.1:5000/kill-process?ID_Folder=1");
        var content = await response.Content.ReadAsStringAsync();
        Console.WriteLine(content);
    }
}
Base URL

All API endpoints are available at: http://127.0.0.1:5000

Authentication

Currently, no authentication is required to access these APIs.

API Endpoints

POST /run-trungfox

Launch a TrungFox browser instance with specific profile settings.

Request Parameters

Parameter Type Required Description
ID_Folder String or Number Yes The profile ID to launch
isServer Boolean No Whether to run in server mode (Default: false)
screen String No Screen dimensions in format "widthxheight" (Default: "0x0")
on_toolbar_close Number No Enable close toolbar button (0 or 1, Default: 0)
on_toolbar_url Number No Enable URL toolbar (0 or 1, Default: 0)

Example Request

cURL
Python
JavaScript
C#
curl -X POST http://127.0.0.1:5000/run-trungfox \
-H "Content-Type: application/json" \
-d '{
    "ID_Folder": 1,
    "isServer": true,
    "screen": "1024x768",
    "on_toolbar_close": 1,
    "on_toolbar_url": 1
}'
import requests
import json

url = "http://127.0.0.1:5000/run-trungfox"
payload = {
    "ID_Folder": 1,
    "isServer": True,
    "screen": "1024x768",
    "on_toolbar_close": 1,
    "on_toolbar_url": 1
}
headers = {"Content-Type": "application/json"}

response = requests.post(url, data=json.dumps(payload), headers=headers)
print(response.json())
fetch('http://127.0.0.1:5000/run-trungfox', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        ID_Folder: 1,
        isServer: true,
        screen: "1024x768",
        on_toolbar_close: 1,
        on_toolbar_url: 1
    })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var url = "http://127.0.0.1:5000/run-trungfox";

        var payload = new
        {
            ID_Folder = 1,
            isServer = true,
            screen = "1024x768",
            on_toolbar_close = 1,
            on_toolbar_url = 1
        };

        var json = JsonSerializer.Serialize(payload);
        var content = new StringContent(json, Encoding.UTF8, "application/json");

        using var client = new HttpClient();
        var response = await client.PostAsync(url, content);
        var responseString = await response.Content.ReadAsStringAsync();

        Console.WriteLine(responseString);
    }
}

Successful Response

{
    "status": "success",
    "ID_Folder": "1",
    "Url_WS": "ws://localhost:55242/8a5d17184bbff2fbf0f5bbf5479742a2",
    "api_stop": "http://127.0.0.1:5000/kill-process?ID_Folder=1"
}

Error Response

{
    "status": "error",
    "error": "Invalid ID_Folder"
}

GET /kill-all-process

Kill all running browser instance.

Example Request

cURL
Python
JavaScript
C#
curl http://127.0.0.1:5000/kill-all-process
import requests

url = "http://127.0.0.1:5000/kill-all-process"

response = requests.get(url)
print(response.json())
fetch('http://127.0.0.1:5000/kill-all-process')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var url = "http://127.0.0.1:5000/kill-all-process";

        using var client = new HttpClient();
        var response = await client.GetAsync(url);
        var responseString = await response.Content.ReadAsStringAsync();

        Console.WriteLine(responseString);
    }
}

Successful Response

{
    "status": "success"
}

Error Response

{
    "status": "error",
    "error": "No process found."
}

GET /kill-process

Kill a running browser instance by profile ID.

Request Parameters

Parameter Type Required Description
ID_Folder String or Number Yes The profile ID to terminate

Example Request

cURL
Python
JavaScript
C#
curl http://127.0.0.1:5000/kill-process?ID_Folder=1
import requests

url = "http://127.0.0.1:5000/kill-process"
params = {"ID_Folder": 1}

response = requests.get(url, params=params)
print(response.json())
fetch('http://127.0.0.1:5000/kill-process?ID_Folder=1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var baseUrl = "http://127.0.0.1:5000/kill-process";
        var query = "?ID_Folder=1";
        var url = baseUrl + query;

        using var client = new HttpClient();
        var response = await client.GetAsync(url);
        var responseString = await response.Content.ReadAsStringAsync();

        Console.WriteLine(responseString);
    }
}

Successful Response

{
    "status": "killed",
    "ID_Folder": "1"
}

Error Response

{
    "status": "error",
    "error": "No process found for this ID_Folder"
}

GET /list-ID_Folder

Get a list of all available profile IDs.

Example Request

cURL
Python
JavaScript
C#
curl http://127.0.0.1:5000/list-ID_Folder
import requests

url = "http://127.0.0.1:5000/list-ID_Folder"
response = requests.get(url)
print(response.json())
fetch('http://127.0.0.1:5000/list-ID_Folder')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var url = "http://127.0.0.1:5000/list-ID_Folder";

        using var client = new HttpClient();
        var response = await client.GetAsync(url);
        var responseString = await response.Content.ReadAsStringAsync();

        Console.WriteLine(responseString);
    }
}

Response

[1, 2, 3, 4, 5]

GET /list-worker-id

Get a list of all workers and their assigned profile IDs.

Example Request

cURL
Python
JavaScript
C#
curl http://127.0.0.1:5000/list-worker-id
import requests

url = "http://127.0.0.1:5000/list-worker-id"
response = requests.get(url)
print(response.json())
fetch('http://127.0.0.1:5000/list-worker-id')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var url = "http://127.0.0.1:5000/list-worker-id";

        using var client = new HttpClient();
        var response = await client.GetAsync(url);
        var responseString = await response.Content.ReadAsStringAsync();

        Console.WriteLine(responseString);
    }
}

Response

[
    {
        "worker_id": 0,
        "ID_Folders": ["1", "3"]
    },
    {
        "worker_id": 1,
        "ID_Folders": ["2"]
    },
    {
        "worker_id": 2,
        "ID_Folders": []
    }
]

GET /reset-worker

Reset a specific worker by ID.

Request Parameters

Parameter Type Required Description
worker_id Number Yes The worker ID to reset

Example Request

cURL
Python
JavaScript
C#
curl http://127.0.0.1:5000/reset-worker?worker_id=1
import requests

url = "http://127.0.0.1:5000/reset-worker"
params = {"worker_id": 1}

response = requests.get(url, params=params)
print(response.json())
fetch('http://127.0.0.1:5000/reset-worker?worker_id=1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var baseUrl = "http://127.0.0.1:5000/reset-worker";
        var query = "?worker_id=1";
        var url = baseUrl + query;

        using var client = new HttpClient();
        var response = await client.GetAsync(url);
        var responseString = await response.Content.ReadAsStringAsync();

        Console.WriteLine(responseString);
    }
}

Successful Response

{
    "status": "restarting",
    "worker_id": 1
}

Error Response

{
    "status": "error",
    "error": "Invalid worker_id"
}

WebSocket API

When launching a browser in server mode, a WebSocket URL is returned. This WebSocket can be used to control the browser remotely.

The WebSocket URL is available in the response of the /run-trungfox endpoint as Url_WS.

WebSocket Automation Examples

Example: Click a Button for 28 Seconds

This example demonstrates how to launch a browser, navigate to a website, and continuously click a button for 28 seconds using WebSockets.

Python (Playwright)
Node.js (Playwright)
C# (Playwright)
import time
import requests
import asyncio
from playwright.async_api import async_playwright

# Configuration
API_URL = "http://127.0.0.1:5000"
PROFILE_ID = 1  # Replace with your profile ID
TARGET_URL = "https://mmo69.com/button-click-me.html"
BUTTON_ID = "randomButton"
DURATION_SECONDS = 28

async def click_button_repeatedly():
    # Step 1: Launch the browser using the API
    print(f"Launching browser with profile {PROFILE_ID}...")
    
    response = requests.post(
        f"{API_URL}/run-trungfox",
        json={
            "ID_Folder": PROFILE_ID,
            "isServer": 1,  # Enable WebSocket mode
            "screen": "1024x768",
            "on_toolbar_close": 1,
            "on_toolbar_url": 1
        }
    )
    
    if response.status_code != 200:
        print(f"Error launching browser: {response.text}")
        return
    
    data = response.json()
    websocket_url = data.get("Url_WS")
    
    if not websocket_url:
        print("No WebSocket URL returned")
        return
    
    print(f"Browser launched with WebSocket URL: {websocket_url}")
    
    try:
        async with async_playwright() as p:
            # Connect to the browser instance via WebSocket
            browser = await p.firefox.connect(websocket_url)
            
            # Create a new page
            page = await browser.new_page()
            
            # Navigate to the target page
            print(f"Navigating to {TARGET_URL}")
            await page.goto(TARGET_URL)
            
            # Wait for the page to load
            await page.wait_for_load_state("networkidle")
            
            # Check if the button exists
            button = await page.query_selector(f"#{BUTTON_ID}")
            if not button:
                print(f"Error: Button with ID '{BUTTON_ID}' not found")
                await browser.close()
                return
            
            print(f"Found button #{BUTTON_ID}, starting to click...")
            
            # Set up click loop
            start_time = time.time()
            end_time = start_time + DURATION_SECONDS
            click_count = 0
            
            while time.time() < end_time:
                # Click the button
                await page.click(f"#{BUTTON_ID}")
                click_count += 1
                
                # Print status every 10 clicks
                if click_count % 10 == 0:
                    elapsed = time.time() - start_time
                    remaining = max(0, DURATION_SECONDS - elapsed)
                    print(f"Clicks: {click_count}, Elapsed: {elapsed:.1f}s, Remaining: {remaining:.1f}s")
                
                # Small delay between clicks
                await asyncio.sleep(0.1)
            
            print(f"Finished clicking. Total clicks: {click_count}")
            
            # Take a screenshot for verification
            screenshot_path = "final_result.png"
            await page.screenshot(path=screenshot_path)
            print(f"Screenshot saved to {screenshot_path}")
            
            # Get the page content
            content = await page.content()
            print(f"Page content length: {len(content)} characters")
            
            # Close the browser
            await browser.close()
    
    except Exception as e:
        print(f"Error during automation: {e}")
    
    finally:
        # Step 3: Clean up - kill the browser process via API
        try:
            print("Terminating browser process...")
            requests.get(f"{API_URL}/kill-process?ID_Folder={PROFILE_ID}")
            print("Browser terminated successfully")
        except Exception as e:
            print(f"Error terminating browser: {e}")

# Run the example
if __name__ == "__main__":
    # Set API key
    response = requests.get("http://127.0.0.1:5000/set-api-key?key=point_xxx")
    print(response.json())

    asyncio.run(click_button_repeatedly())
const { chromium, firefox } = require('playwright');
const axios = require('axios');
const fs = require('fs');

// Configuration
const API_URL = 'http://127.0.0.1:5000';
const PROFILE_ID = 1;  // Replace with your profile ID
const TARGET_URL = 'https://mmo69.com/button-click-me.html';
const BUTTON_ID = 'randomButton';
const DURATION_SECONDS = 28;

async function clickButtonRepeatedly() {
  console.log(`Launching browser with profile ${PROFILE_ID}...`);
  
  try {
    // Step 1: Set API key
    await axios.get(`${API_URL}/set-api-key?key=point_xxx`);
    
    // Step 2: Launch the browser using the API
    const response = await axios.post(`${API_URL}/run-trungfox`, {
      ID_Folder: PROFILE_ID,
      isServer: true,
      screen: '1024x768',
      on_toolbar_close: 1,
      on_toolbar_url: 1
    });
    
    const data = response.data;
    const websocketUrl = data.Url_WS;
    
    if (!websocketUrl) {
      console.error('No WebSocket URL returned');
      return;
    }
    
    console.log(`Browser launched with WebSocket URL: ${websocketUrl}`);
    
    // Step 3: Connect to the browser instance via WebSocket
    const browser = await firefox.connect({ wsEndpoint: websocketUrl });
    
    // Create a new page
    const page = await browser.newPage();
    
    // Navigate to the target page
    console.log(`Navigating to ${TARGET_URL}`);
    await page.goto(TARGET_URL);
    
    // Wait for the page to load
    await page.waitForLoadState('networkidle');
    
    // Check if the button exists
    const button = await page.$(`.${BUTTON_ID}`) || await page.$(`#${BUTTON_ID}`);
    if (!button) {
      console.error(`Error: Button with ID '${BUTTON_ID}' not found`);
      await browser.close();
      return;
    }
    
    console.log(`Found button #${BUTTON_ID}, starting to click...`);
    
    // Set up click loop
    const startTime = Date.now();
    const endTime = startTime + (DURATION_SECONDS * 1000);
    let clickCount = 0;
    
    // Click loop
    while (Date.now() < endTime) {
      // Click the button
      await page.click(`#${BUTTON_ID}`);
      clickCount++;
      
      // Print status every 10 clicks
      if (clickCount % 10 === 0) {
        const elapsed = (Date.now() - startTime) / 1000;
        const remaining = Math.max(0, DURATION_SECONDS - elapsed);
        console.log(`Clicks: ${clickCount}, Elapsed: ${elapsed.toFixed(1)}s, Remaining: ${remaining.toFixed(1)}s`);
      }
      
      // Small delay between clicks
      await page.waitForTimeout(100);
    }
    
    console.log(`Finished clicking. Total clicks: ${clickCount}`);
    
    // Take a screenshot for verification
    const screenshotPath = 'final_result.png';
    await page.screenshot({ path: screenshotPath });
    console.log(`Screenshot saved to ${screenshotPath}`);
    
    // Get the page content
    const content = await page.content();
    console.log(`Page content length: ${content.length} characters`);
    
    // Close the browser connection
    await browser.close();
    
    // Step 4: Clean up - kill the browser process via API
    console.log('Terminating browser process...');
    await axios.get(`${API_URL}/kill-process?ID_Folder=${PROFILE_ID}`);
    console.log('Browser terminated successfully');
  
  } catch (error) {
    console.error('Error during automation:', error.message);
  }
}

// Run the example
clickButtonRepeatedly().catch(console.error);
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Playwright;
using System.Diagnostics;

class ButtonClickerExample
{
    // Configuration
    private const string API_URL = "http://127.0.0.1:5000";
    private const int PROFILE_ID = 1; // Replace with your profile ID
    private const string TARGET_URL = "https://mmo69.com/button-click-me.html";
    private const string BUTTON_ID = "randomButton";
    private const int DURATION_SECONDS = 28;

    static async Task Main()
    {
        // Set API key
        await SetApiKey();
        
        // Run the automation
        await ClickButtonRepeatedly();
    }

    static async Task SetApiKey()
    {
        try
        {
            using var httpClient = new HttpClient();
            var response = await httpClient.GetAsync($"{API_URL}/set-api-key?key=point_xxx");
            response.EnsureSuccessStatusCode();
            
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine($"API Key set: {content}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error setting API key: {ex.Message}");
        }
    }

    static async Task ClickButtonRepeatedly()
    {
        Console.WriteLine($"Launching browser with profile {PROFILE_ID}...");
        
        string websocketUrl = null;
        
        try
        {
            // Step 1: Launch the browser using the API
            using var httpClient = new HttpClient();
            
            var requestData = new
            {
                ID_Folder = PROFILE_ID,
                isServer = true,
                screen = "1024x768",
                on_toolbar_close = 1,
                on_toolbar_url = 1
            };
            
            var jsonContent = JsonSerializer.Serialize(requestData);
            var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
            
            var response = await httpClient.PostAsync($"{API_URL}/run-trungfox", content);
            
            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine($"Error launching browser: {await response.Content.ReadAsStringAsync()}");
                return;
            }
            
            var responseData = await response.Content.ReadAsStringAsync();
            var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
            var responseObj = JsonSerializer.Deserialize(responseData, options);
            
            websocketUrl = responseObj.Url_WS;
            
            if (string.IsNullOrEmpty(websocketUrl))
            {
                Console.WriteLine("No WebSocket URL returned");
                return;
            }
            
            Console.WriteLine($"Browser launched with WebSocket URL: {websocketUrl}");
            
            // Step 2: Connect to the browser instance via WebSocket and automate
            using var playwright = await Playwright.CreateAsync();
            var browser = await playwright.Firefox.ConnectAsync(websocketUrl);
            
            // Create a new page
            var page = await browser.NewPageAsync();
            
            // Navigate to the target page
            Console.WriteLine($"Navigating to {TARGET_URL}");
            await page.GotoAsync(TARGET_URL);
            
            // Wait for the page to load
            await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
            
            // Check if the button exists
            var button = await page.QuerySelectorAsync($"#{BUTTON_ID}");
            if (button == null)
            {
                Console.WriteLine($"Error: Button with ID '{BUTTON_ID}' not found");
                await browser.CloseAsync();
                return;
            }
            
            Console.WriteLine($"Found button #{BUTTON_ID}, starting to click...");
            
            // Set up click loop
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            int clickCount = 0;
            
            while (stopwatch.ElapsedMilliseconds < DURATION_SECONDS * 1000)
            {
                // Click the button
                await page.ClickAsync($"#{BUTTON_ID}");
                clickCount++;
                
                // Print status every 10 clicks
                if (clickCount % 10 == 0)
                {
                    var elapsed = stopwatch.ElapsedMilliseconds / 1000.0;
                    var remaining = Math.Max(0, DURATION_SECONDS - elapsed);
                    Console.WriteLine($"Clicks: {clickCount}, Elapsed: {elapsed:F1}s, Remaining: {remaining:F1}s");
                }
                
                // Small delay between clicks
                await Task.Delay(100);
            }
            
            Console.WriteLine($"Finished clicking. Total clicks: {clickCount}");
            
            // Take a screenshot for verification
            string screenshotPath = "final_result.png";
            await page.ScreenshotAsync(new PageScreenshotOptions { Path = screenshotPath });
            Console.WriteLine($"Screenshot saved to {screenshotPath}");
            
            // Get the page content
            var content = await page.ContentAsync();
            Console.WriteLine($"Page content length: {content.Length} characters");
            
            // Close the browser
            await browser.CloseAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error during automation: {ex.Message}");
            Console.WriteLine(ex.StackTrace);
        }
        finally
        {
            // Step 3: Clean up - kill the browser process via API
            try
            {
                Console.WriteLine("Terminating browser process...");
                using var httpClient = new HttpClient();
                await httpClient.GetAsync($"{API_URL}/kill-process?ID_Folder={PROFILE_ID}");
                Console.WriteLine("Browser terminated successfully");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error terminating browser: {ex.Message}");
            }
        }
    }

    // Response class for deserializing the API response
    private class BrowserResponse
    {
        public string Status { get; set; }
        public string ID_Folder { get; set; }
        public string Url_WS { get; set; }
        public string Api_stop { get; set; }
    }
}

Advanced Example: Auto-Clicking with Performance Monitoring

This more advanced example includes error handling, performance monitoring, and detailed statistics.

Python (Playwright)
Node.js (Playwright)
import time
import requests
import asyncio
import json
from datetime import datetime
import os
from playwright.async_api import async_playwright
import traceback

# Configuration
API_URL = "http://127.0.0.1:5000"
PROFILE_ID = 1
TARGET_URL = "https://mmo69.com/button-click-me.html"
BUTTON_ID = "randomButton"
DURATION_SECONDS = 28
SCREENSHOT_FOLDER = "screenshots"
TAKE_SCREENSHOTS = True
SCREENSHOT_INTERVAL = 5  # seconds

async def run_automated_task():
    print(f"Starting automated clicking task for {TARGET_URL}")
    print(f"Using profile ID: {PROFILE_ID}, Duration: {DURATION_SECONDS} seconds")
    
    # Create screenshots directory if needed
    if TAKE_SCREENSHOTS:
        os.makedirs(SCREENSHOT_FOLDER, exist_okay=True)
    
    # Step 1: Launch browser via TFLogin API
    try:
        response = requests.post(
            f"{API_URL}/run-trungfox",
            json={
                "ID_Folder": PROFILE_ID,
                "isServer": 1,  # Enable WebSocket mode (1 = true in the API)
                "screen": "1024x768",
                "on_toolbar_close": 1,
                "on_toolbar_url": 1
            },
            headers={"Content-Type": "application/json"}
        )
        
        response.raise_for_status()
        data = response.json()
        
        websocket_url = data.get("Url_WS")
        if not websocket_url:
            raise ValueError("No WebSocket URL returned in the API response")
            
        print(f"Browser launched successfully")
        print(f"WebSocket URL: {websocket_url}")
        print(f"API stop URL: {data.get('api_stop')}")
        
    except Exception as e:
        print(f"Failed to launch browser: {e}")
        traceback.print_exc()
        return
    
    # Step 2: Connect to browser and perform automation
    try:
        async with async_playwright() as p:
            # Connect to browser using the WebSocket URL
            browser = await p.firefox.connect(websocket_url)
            print("Successfully connected to browser")
            
            # Create a new page
            page = await browser.new_page()
            
            # Navigate to target URL
            print(f"Navigating to {TARGET_URL}")
            await page.goto(TARGET_URL)
            
            # Take initial screenshot
            if TAKE_SCREENSHOTS:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                screenshot_path = f"{SCREENSHOT_FOLDER}/initial_{timestamp}.png"
                await page.screenshot(path=screenshot_path)
                print(f"Initial screenshot saved: {screenshot_path}")
            
            # Verify the button exists
            button = await page.query_selector(f"#{BUTTON_ID}")
            if not button:
                print(f"ERROR: Button with ID '{BUTTON_ID}' not found on the page")
                await browser.close()
                return
                
            print(f"Button '{BUTTON_ID}' found, starting click sequence...")
            
            # Set up click loop with performance tracking
            start_time = time.time()
            end_time = start_time + DURATION_SECONDS
            click_count = 0
            last_screenshot_time = start_time
            performance_data = []
            
            while time.time() < end_time:
                current_time = time.time()
                elapsed = current_time - start_time
                remaining = max(0, DURATION_SECONDS - elapsed)
                
                try:
                    # Click the button
                    await page.click(f"#{BUTTON_ID}")
                    click_count += 1
                    
                    # Record performance data every 10 clicks
                    if click_count % 10 == 0:
                        click_rate = click_count / elapsed if elapsed > 0 else 0
                        performance_data.append({
                            "elapsed_seconds": elapsed,
                            "clicks": click_count,
                            "clicks_per_second": click_rate
                        })
                        
                        print(f"Clicks: {click_count}, Rate: {click_rate:.2f}/sec, Remaining: {remaining:.1f}s")
                    
                    # Take screenshot at intervals
                    if TAKE_SCREENSHOTS and (current_time - last_screenshot_time >= SCREENSHOT_INTERVAL):
                        last_screenshot_time = current_time
                        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                        screenshot_path = f"{SCREENSHOT_FOLDER}/progress_{timestamp}_{click_count}.png"
                        await page.screenshot(path=screenshot_path)
                        print(f"Progress screenshot saved: {screenshot_path}")
                    
                except Exception as e:
                    print(f"Error during click operation: {e}")
                
                # Small delay between clicks
                await asyncio.sleep(0.1)
            
            # Calculate final statistics
            total_time = time.time() - start_time
            click_rate = click_count / total_time if total_time > 0 else 0
            
            print("\n=== Task Complete ===")
            print(f"Total clicks: {click_count}")
            print(f"Total time: {total_time:.2f} seconds")
            print(f"Average click rate: {click_rate:.2f} clicks per second")
            
            # Save performance report
            report_data = {
                "summary": {
                    "url": TARGET_URL,
                    "button_id": BUTTON_ID,
                    "total_clicks": click_count,
                    "total_seconds": total_time,
                    "avg_click_rate": click_rate
                },
                "timeline": performance_data
            }
            
            with open(f"{SCREENSHOT_FOLDER}/performance_report.json", "w") as f:
                json.dump(report_data, f, indent=2)
            
            # Take final screenshot
            if TAKE_SCREENSHOTS:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                screenshot_path = f"{SCREENSHOT_FOLDER}/final_{timestamp}.png"
                await page.screenshot(path=screenshot_path)
                print(f"Final screenshot saved: {screenshot_path}")
            
            # Close browser connection
            await browser.close()

    except Exception as e:
        print(f"Automation error: {e}")
        traceback.print_exc()

    finally:
        # Clean up: kill the browser process
        try:
            print("Terminating browser via API...")
            requests.get(f"{API_URL}/kill-process?ID_Folder={PROFILE_ID}")
            print("Browser terminated successfully")
        except Exception as e:
            print(f"Error terminating browser: {e}")

if __name__ == "__main__":
    # Set API key
    response = requests.get("http://127.0.0.1:5000/set-api-key?key=point_xxx")
    print(response.json())

    asyncio.run(run_automated_task())
const { firefox } = require('playwright');
const axios = require('axios');
const fs = require('fs');
const path = require('path');

// Configuration
const API_URL = 'http://127.0.0.1:5000';
const PROFILE_ID = 1;
const TARGET_URL = 'https://mmo69.com/button-click-me.html';
const BUTTON_ID = 'randomButton';
const DURATION_SECONDS = 28;
const SCREENSHOT_FOLDER = 'screenshots';
const TAKE_SCREENSHOTS = true;
const SCREENSHOT_INTERVAL = 5; // seconds

async function runAutomatedTask() {
  console.log(`Starting automated clicking task for ${TARGET_URL}`);
  console.log(`Using profile ID: ${PROFILE_ID}, Duration: ${DURATION_SECONDS} seconds`);
  
  // Create screenshots directory if needed
  if (TAKE_SCREENSHOTS) {
    if (!fs.existsSync(SCREENSHOT_FOLDER)) {
      fs.mkdirSync(SCREENSHOT_FOLDER, { recursive: true });
    }
  }
  
  // Step 1: Set API key
  try {
    await axios.get(`${API_URL}/set-api-key?key=point_xxx`);
    console.log('API key set successfully');
  } catch (error) {
    console.error('Failed to set API key:', error.message);
    return;
  }
  
  let browserData;
  
  try {
    // Step 2: Launch browser via TFLogin API
    const response = await axios.post(`${API_URL}/run-trungfox`, {
      ID_Folder: PROFILE_ID,
      isServer: true,
      screen: '1024x768',
      on_toolbar_close: 1,
      on_toolbar_url: 1
    }, {
      headers: { 'Content-Type': 'application/json' }
    });
    
    browserData = response.data;
    
    const websocketUrl = browserData.Url_WS;
    if (!websocketUrl) {
      throw new Error('No WebSocket URL returned in the API response');
    }
    
    console.log('Browser launched successfully');
    console.log(`WebSocket URL: ${websocketUrl}`);
    console.log(`API stop URL: ${browserData.api_stop}`);
    
    // Step 3: Connect to browser and perform automation
    const browser = await firefox.connect({ wsEndpoint: websocketUrl });
    console.log('Successfully connected to browser');
    
    // Create a new page
    const page = await browser.newPage();
    
    // Navigate to target URL
    console.log(`Navigating to ${TARGET_URL}`);
    await page.goto(TARGET_URL);
    
    // Take initial screenshot
    if (TAKE_SCREENSHOTS) {
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const screenshotPath = path.join(SCREENSHOT_FOLDER, `initial_${timestamp}.png`);
      await page.screenshot({ path: screenshotPath });
      console.log(`Initial screenshot saved: ${screenshotPath}`);
    }
    
    // Verify the button exists
    const button = await page.$(`#${BUTTON_ID}`);
    if (!button) {
      console.error(`ERROR: Button with ID '${BUTTON_ID}' not found on the page`);
      await browser.close();
      return;
    }
    
    console.log(`Button '${BUTTON_ID}' found, starting click sequence...`);
    
    // Set up click loop with performance tracking
    const startTime = Date.now();
    const endTime = startTime + (DURATION_SECONDS * 1000);
    let clickCount = 0;
    let lastScreenshotTime = startTime;
    const performanceData = [];
    
    while (Date.now() < endTime) {
      const currentTime = Date.now();
      const elapsed = (currentTime - startTime) / 1000;
      const remaining = Math.max(0, DURATION_SECONDS - elapsed);
      
      try {
        // Click the button
        await page.click(`#${BUTTON_ID}`);
        clickCount++;
        
        // Record performance data every 10 clicks
        if (clickCount % 10 === 0) {
          const clickRate = clickCount / elapsed;
          performanceData.push({
            elapsed_seconds: elapsed,
            clicks: clickCount,
            clicks_per_second: clickRate
          });
          
          console.log(`Clicks: ${clickCount}, Rate: ${clickRate.toFixed(2)}/sec, Remaining: ${remaining.toFixed(1)}s`);
        }
        
        // Take screenshot at intervals
        if (TAKE_SCREENSHOTS && ((currentTime - lastScreenshotTime) >= SCREENSHOT_INTERVAL * 1000)) {
          lastScreenshotTime = currentTime;
          const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
          const screenshotPath = path.join(SCREENSHOT_FOLDER, `progress_${timestamp}_${clickCount}.png`);
          await page.screenshot({ path: screenshotPath });
          console.log(`Progress screenshot saved: ${screenshotPath}`);
        }
      } catch (error) {
        console.error(`Error during click operation: ${error.message}`);
      }
      
      // Small delay between clicks
      await page.waitForTimeout(100);
    }
    
    // Calculate final statistics
    const totalTime = (Date.now() - startTime) / 1000;
    const clickRate = clickCount / totalTime;
    
    console.log('\n=== Task Complete ===');
    console.log(`Total clicks: ${clickCount}`);
    console.log(`Total time: ${totalTime.toFixed(2)} seconds`);
    console.log(`Average click rate: ${clickRate.toFixed(2)} clicks per second`);
    
    // Save performance report
    const reportData = {
      summary: {
        url: TARGET_URL,
        button_id: BUTTON_ID,
        total_clicks: clickCount,
        total_seconds: totalTime,
        avg_click_rate: clickRate
      },
      timeline: performanceData
    };
    
    fs.writeFileSync(
      path.join(SCREENSHOT_FOLDER, 'performance_report.json'),
      JSON.stringify(reportData, null, 2)
    );
    
    // Take final screenshot
    if (TAKE_SCREENSHOTS) {
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const screenshotPath = path.join(SCREENSHOT_FOLDER, `final_${timestamp}.png`);
      await page.screenshot({ path: screenshotPath });
      console.log(`Final screenshot saved: ${screenshotPath}`);
    }
    
    // Close browser connection
    await browser.close();
    
  } catch (error) {
    console.error(`Automation error: ${error.message}`);
    console.error(error.stack);
  } finally {
    // Clean up: kill the browser process
    try {
      console.log('Terminating browser via API...');
      await axios.get(`${API_URL}/kill-process?ID_Folder=${PROFILE_ID}`);
      console.log('Browser terminated successfully');
    } catch (error) {
      console.error(`Error terminating browser: ${error.message}`);
    }
  }
}

// Run the example
runAutomatedTask().catch(console.error);

Multi-Thread Example: Managing Multiple Browser Instances with Button Clicking

This advanced example demonstrates how to run multiple browser instances with different profiles, limited by a maximum thread count.

Python
Node.js
import requests
import json
import threading
import time
import win32gui
import win32api
import win32con
import queue
import sys
import asyncio
from playwright.async_api import async_playwright
from datetime import datetime
import os

# Configuration
API_URL = "http://127.0.0.1:5000"
MAX_THREADS = 6  # Maximum number of concurrent browser instances
WINDOW_WIDTH = 222
WINDOW_HEIGHT = 345
TARGET_URL = "https://mmo69.com/button-click-me.html"
BUTTON_ID = "randomButton"  # ID of the button to click
CLICK_DURATION = 28  # How many seconds to click the button
RUN_DURATION = 60  # Total time to run each browser instance
SCREENSHOT_DIR = "screenshots"

# Thread-safe queue for managing profile IDs
profile_queue = queue.Queue()
active_threads = set()  # Track running threads
thread_lock = threading.Lock()  # For thread-safe operations
thread_complete_event = threading.Event()  # Signal when a thread completes

def enum_windows_callback(hwnd, windows):
    """Callback for EnumWindows to find Firefox windows"""
    if win32gui.IsWindowVisible(hwnd) and win32gui.GetClassName(hwnd) == "MozillaWindowClass":
        windows.append(hwnd)

def arrange_firefox_windows():
    """Arrange Firefox windows in a grid layout on the screen"""
    windows = []
    win32gui.EnumWindows(enum_windows_callback, windows)
    
    screen_width = win32api.GetSystemMetrics(0)  # SM_CXSCREEN
    screen_height = win32api.GetSystemMetrics(1)  # SM_CYSCREEN
    
    if not windows:
        print("Couldn't find any Firefox windows.")
        return
    
    cols = max(1, screen_width // WINDOW_WIDTH)
    
    print(f"Arranging {len(windows)} Firefox windows...")
    for idx, hwnd in enumerate(windows):
        row = idx // cols
        col = idx % cols
        x = col * WINDOW_WIDTH
        y = row * WINDOW_HEIGHT
        win32gui.MoveWindow(hwnd, x, y, WINDOW_WIDTH, WINDOW_HEIGHT, True)
    print("Windows arranged successfully")

def get_available_profiles():
    """Get all available profile IDs from the API"""
    try:
        response = requests.get(f"{API_URL}/list-ID_Folder")
        if response.status_code == 200:
            profiles = response.json()
            print(f"Found {len(profiles)} available profiles")
            return profiles
        else:
            print(f"Error getting profiles: {response.status_code} - {response.text}")
            return []
    except Exception as e:
        print(f"Exception getting profiles: {e}")
        return []

async def click_button_task(websocket_url, profile_id, thread_name):
    """Task to click the button on the webpage for a specific duration"""
    try:
        console.log(`[${thread_name}] Connecting to browser via WebSocket...`);
        
        async with async_playwright() as p:
            # Connect to the browser via WebSocket
            browser = await p.firefox.connect(websocket_url);
            
            # Create a new page
            page = await browser.new_page();
            
            # Navigate to the target URL
            console.log(`[${thread_name}] Navigating to ${TARGET_URL}`);
            await page.goto(TARGET_URL);
            
            # Wait for the page to load
            await page.wait_for_load_state("networkidle");
            
            # Check if the button exists
            button = await page.query_selector(f"#{BUTTON_ID}");
            if not button:
                console.log(`[${thread_name}] Error: Button with ID '${BUTTON_ID}' not found`);
                await browser.close();
                return;
            }
            
            console.log(`[${thread_name}] Found button #${BUTTON_ID}, starting to click...`);
            
            # Set up click loop
            start_time = time.time();
            end_time = start_time + CLICK_DURATION;
            click_count = 0;
            
            # Create screenshot directory for this profile
            profile_screenshot_dir = os.path.join(SCREENSHOT_DIR, `profile_${profile_id}`);
            os.makedirs(profile_screenshot_dir, exist_ok=True);
            
            # Take initial screenshot
            await page.screenshot(path=f"{profile_screenshot_dir}/initial.png");
            
            while time.time() < end_time:
                # Click the button
                await page.click(f"#{BUTTON_ID}");
                click_count += 1;
                
                # Print status every 10 clicks
                if click_count % 10 == 0:
                    elapsed = time.time() - start_time;
                    remaining = max(0, CLICK_DURATION - elapsed);
                    console.log(`[${thread_name}] Clicks: ${click_count}, Elapsed: ${elapsed:.1f}s, Remaining: ${remaining:.1f}s`);
                
                # Small delay between clicks
                await asyncio.sleep(0.1);
            
            console.log(`[${thread_name}] Finished clicking. Total clicks: ${click_count}`);
            
            # Take a final screenshot
            await page.screenshot(path=f"{profile_screenshot_dir}/final.png");
            
            # Wait for any remaining time in RUN_DURATION
            remaining_time = RUN_DURATION - (time.time() - start_time);
            if remaining_time > 0:
                console.log(`[${thread_name}] Waiting for remaining ${remaining_time:.1f} seconds...`);
                await asyncio.sleep(remaining_time);
            
            # Close browser connection
            await browser.close();
            
    } catch (Exception e) {
        console.log(`[${thread_name}] Error during button clicking: ${e}`);
    }
}

def run_browser_instance(profile_id):
    """Run a browser instance with the given profile ID"""
    thread_name = threading.current_thread().name
    console.log(`[${thread_name}] Starting browser with profile ID: ${profile_id}`);
    
    try {
        # Launch browser with WebSocket server
        response = requests.post(
            f"{API_URL}/run-trungfox",
            json={
                "ID_Folder": profile_id,
                "isServer": True,
                "screen": f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}",
                "on_toolbar_close": 0,
                "on_toolbar_url": 1
            }
        );
        
        if response.status_code != 200:
            console.log(`[${thread_name}] Error launching browser: ${response.text}`);
            return;
            
        data = response.json();
        browser_id = data.get("ID_Folder");
        websocket_url = data.get("Url_WS");
        
        if not websocket_url:
            console.log(`[${thread_name}] No WebSocket URL returned, cannot control browser.`);
            return;
            
        console.log(`[${thread_name}] Browser launched successfully with ID: ${browser_id}`);
        console.log(`[${thread_name}] WebSocket URL: ${websocket_url}`);
        
        # Run the button clicking task in a separate asyncio loop
        asyncio.run(click_button_task(websocket_url, profile_id, thread_name));
        
        # Kill browser instance
        kill_url = f"{API_URL}/kill-process?ID_Folder={profile_id}";
        kill_response = requests.get(kill_url);
        
        if kill_response.status_code == 200:
            console.log(`[${thread_name}] Browser killed successfully`);
        else:
            console.log(`[${thread_name}] Error killing browser: ${kill_response.text}`);
    
    } catch (Exception e) {
        console.log(`[${thread_name}] Exception in thread: ${e}`);
    
    } finally {
        # Signal that this thread has completed
        with thread_lock:
            if profile_id in active_threads:
                active_threads.remove(profile_id);
        thread_complete_event.set();  # Signal that a thread has completed
}

def worker_manager():
    """Main function to manage worker threads"""
    # Create screenshots directory
    os.makedirs(SCREENSHOT_DIR, exist_ok=True);
    
    # Get all available profiles
    profile_ids = get_available_profiles();
    
    if not profile_ids:
        console.log('No profiles available. Exiting.');
        return;
    
    # Add all profile IDs to the queue
    for profile_id in profile_ids:
        profile_queue.put(profile_id);
    
    total_profiles = profile_queue.qsize();
    completed_profiles = 0;
    
    console.log(`Starting browser automation with ${MAX_THREADS} max concurrent instances`);
    console.log(`Each browser will click button '${BUTTON_ID}' for ${CLICK_DURATION} seconds`);
    
    # Process profiles until the queue is empty
    while not profile_queue.empty() or active_threads:
        # Start new threads if we're under the limit and have profiles waiting
        while len(active_threads) < MAX_THREADS and not profile_queue.empty():
            next_profile = profile_queue.get();
            
            # Create and start thread
            with thread_lock:
                active_threads.add(next_profile);
                
            thread = threading.Thread(
                target=run_browser_instance,
                args=(next_profile,),
                name=f"Browser-{next_profile}"
            );
            thread.daemon = True;
            thread.start();
            
            completed_profiles += 1;
            console.log(`Started ${completed_profiles}/${total_profiles} profiles. Active: ${len(active_threads)}`);
            time.sleep(1);  # Small delay between launching instances
        
        # Once we've hit our thread limit, arrange the windows
        if len(active_threads) == MAX_THREADS or profile_queue.empty():
            time.sleep(2);  # Give browsers time to launch
            arrange_firefox_windows();
        
        # Wait for a thread to complete before checking again
        thread_complete_event.wait(timeout=5);
        thread_complete_event.clear();
    
    console.log('All browser instances processed successfully!');
}

if __name__ == "__main__":
    # Set API key
    response = requests.get("http://127.0.0.1:5000/set-api-key?key=point_xxx");
    console.log(response.json());

    try {
        worker_manager();
    } catch (KeyboardInterrupt:
        console.log('\nExiting due to user interrupt');
        # Attempt to clean up any remaining browsers
        try {
            active_ids = list(active_threads);
            for profile_id in active_ids:
                try {
                    console.log(`Cleaning up browser for profile ${profile_id}...`);
                    requests.get(f"{API_URL}/kill-process?ID_Folder={profile_id}");
                } catch (Exception e) {
                    console.log(`Error during cleanup: ${e}`);
                }
        } catch (Exception e) {
            console.log(`Error during final cleanup: ${e}`);
        }
        sys.exit(0);
const axios = require('axios');
const { firefox } = require('playwright');
const fs = require('fs');
const path = require('path');
const os = require('os');

// Configuration
const API_URL = 'http://127.0.0.1:5000';
const MAX_CONCURRENT_BROWSERS = 4;
const WINDOW_WIDTH = 222;
const WINDOW_HEIGHT = 345;
const TARGET_URL = 'https://mmo69.com/button-click-me.html';
const BUTTON_ID = 'randomButton';
const CLICK_DURATION = 28; // seconds
const RUN_DURATION = 60; // seconds
const SCREENSHOT_DIR = 'screenshots';

// State tracking
let activeProfiles = new Set();
let availableProfiles = [];
let completedProfiles = 0;

// Create screenshots directory
if (!fs.existsSync(SCREENSHOT_DIR)) {
  fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
}

// Helper function for delay
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Get available profile IDs from the API
async function getAvailableProfiles() {
  try {
    const response = await axios.get(`${API_URL}/list-ID_Folder`);
    const profiles = response.data;
    console.log(`Found ${profiles.length} available profiles`);
    return profiles;
  } catch (error) {
    console.error(`Error getting profiles: ${error.message}`);
    return [];
  }
}

// Click button task for a specific browser instance
async function clickButtonTask(websocketUrl, profileId, instanceName) {
  try {
    console.log(`[${instanceName}] Connecting to browser via WebSocket...`);
    
    const browser = await firefox.connect({ wsEndpoint: websocketUrl });
    const page = await browser.newPage();
    
    // Navigate to the target URL
    console.log(`[${instanceName}] Navigating to ${TARGET_URL}`);
    await page.goto(TARGET_URL);
    await page.waitForLoadState('networkidle');
    
    // Check if the button exists
    const button = await page.$(`#${BUTTON_ID}`);
    if (!button) {
      console.error(`[${instanceName}] Error: Button with ID '${BUTTON_ID}' not found`);
      await browser.close();
      return;
    }
    
    console.log(`[${instanceName}] Found button #${BUTTON_ID}, starting to click...`);
    
    // Create a new directory for screenshots
    const profileDir = path.join(SCREENSHOT_DIR, `profile_${profileId}`);
    fs.mkdirSync(profileDir, { recursive: true });
    
    // Take initial screenshot
    await page.screenshot({ path: path.join(profileDir, 'initial.png') });
    
    // Set up click loop
    const startTime = Date.now();
    const endTime = startTime + (CLICK_DURATION * 1000);
    let clickCount = 0;
    
    while (Date.now() < endTime) {
      // Click the button
      await page.click(`#${BUTTON_ID}`);
      clickCount++;
      
      // Log status every 10 clicks
      if (clickCount % 10 === 0) {
        const elapsed = (Date.now() - startTime) / 1000;
        const remaining = Math.max(0, CLICK_DURATION - elapsed);
        console.log(`[${instanceName}] Clicks: ${clickCount}, Elapsed: ${elapsed.toFixed(1)}s, Remaining: ${remaining.toFixed(1)}s`);
      }
      
      // Small delay between clicks
      await page.waitForTimeout(100);
    }
    
    console.log(`[${instanceName}] Finished clicking. Total clicks: ${clickCount}`);
    
    // Take a final screenshot
    await page.screenshot({ path: path.join(profileDir, 'final.png') });
    
    // Wait for any remaining time in RUN_DURATION
    const elapsedTotal = (Date.now() - startTime) / 1000;
    const remainingTime = RUN_DURATION - elapsedTotal;
    if (remainingTime > 0) {
      console.log(`[${instanceName}] Waiting for remaining ${remainingTime.toFixed(1)} seconds...`);
      await delay(remainingTime * 1000);
    }
    
    // Close browser connection
    await browser.close();
  } catch (error) {
    console.error(`[${instanceName}] Error during button clicking: ${error.message}`);
  }
}

// Run a browser instance with the given profile ID
async function runBrowserInstance(profileId) {
  const instanceName = `Browser-${profileId}`;
  console.log(`[${instanceName}] Starting browser with profile ID: ${profileId}`);
  
  try {
    // Set API key
    await axios.get(`${API_URL}/set-api-key?key=point_xxx`);
    
    // Launch browser with WebSocket server
    const response = await axios.post(`${API_URL}/run-trungfox`, {
      ID_Folder: profileId,
      isServer: true,
      screen: `${WINDOW_WIDTH}x${WINDOW_HEIGHT}`,
      on_toolbar_close: 0,
      on_toolbar_url: 1
    });
    
    const data = response.data;
    const browserId = data.ID_Folder;
    const websocketUrl = data.Url_WS;
    
    if (!websocketUrl) {
      console.error(`[${instanceName}] No WebSocket URL returned, cannot control browser.`);
      return;
    }
    
    console.log(`[${instanceName}] Browser launched successfully with ID: ${browserId}`);
    console.log(`[${instanceName}] WebSocket URL: ${websocketUrl}`);
    
    // Run the button clicking task
    await clickButtonTask(websocketUrl, profileId, instanceName);
    
    // Kill browser instance
    const killResponse = await axios.get(`${API_URL}/kill-process?ID_Folder=${profileId}`);
    console.log(`[${instanceName}] Browser killed successfully`);
    
  } catch (error) {
    console.error(`[${instanceName}] Exception: ${error.message}`);
  } finally {
    // Signal that this browser has completed
    activeProfiles.delete(profileId);
    process.nextTick(processQueue); // Continue processing the queue
  }
}

// Process the queue of profiles
async function processQueue() {
  // If we're already at max concurrency or have no more profiles, just return
  if (activeProfiles.size >= MAX_CONCURRENT_BROWSERS || availableProfiles.length === 0) {
    return;
  }
  
  // Start new browsers until we reach max concurrency
  while (activeProfiles.size < MAX_CONCURRENT_BROWSERS && availableProfiles.length > 0) {
    const nextProfile = availableProfiles.shift();
    activeProfiles.add(nextProfile);
    
    completedProfiles++;
    console.log(`Started ${completedProfiles}/${totalProfiles} profiles. Active: ${activeProfiles.size}`);
    
    // Start browser instance (don't await, let it run in parallel)
    runBrowserInstance(nextProfile).catch(console.error);
    
    // Small delay between launching instances
    await delay(1000);
  }
}

// Main function
async function main() {
  try {
    // Set API key first
    await axios.get(`${API_URL}/set-api-key?key=point_xxx`);
    console.log('API key set');
    
    // Get available profiles
    availableProfiles = await getAvailableProfiles();
    
    if (availableProfiles.length === 0) {
      console.log('No profiles available. Exiting.');
      return;
    }
    
    // Store total for reporting
    totalProfiles = availableProfiles.length;
    
    console.log(`Starting browser automation with ${MAX_CONCURRENT_BROWSERS} max concurrent instances`);
    console.log(`Each browser will click button '${BUTTON_ID}' for ${CLICK_DURATION} seconds`);
    
    // Start processing the queue
    await processQueue();
    
    // Wait until all browsers are done
    const checkInterval = setInterval(() => {
      if (activeProfiles.size === 0 && availableProfiles.length === 0) {
        clearInterval(checkInterval);
        console.log('All browser instances processed successfully!');
      }
    }, 1000);
    
  } catch (error) {
    console.error(`Error in main process: ${error.message}`);
  }
}

// Handle cleanup on exit
process.on('SIGINT', async () => {
  console.log('\nExiting due to user interrupt');
  
  // Clean up any remaining browsers
  try {
    const activeIds = Array.from(activeProfiles);
    for (const profileId of activeIds) {
      try {
        console.log(`Cleaning up browser for profile ${profileId}...`);
        await axios.get(`${API_URL}/kill-process?ID_Folder=${profileId}`);
      } catch (error) {
        console.error(`Error during cleanup: ${error.message}`);
      }
    }
  } catch (error) {
    console.error(`Error during final cleanup: ${error.message}`);
  }
  
  process.exit(0);
});

// Installation requirements information
console.log('Required npm packages for this script:');
console.log('npm install axios playwright');

// Run the main function
main().catch(console.error);

Installation Requirements for Examples

Python
Node.js
C#
pip install requests pywin32 asyncio playwright==1.50.0

# Install Playwright browsers
python -m playwright install firefox
npm install axios playwright@1.50.0

# Install Playwright browsers
npx playwright install firefox
# Install the Microsoft.Playwright NuGet package
dotnet add package Microsoft.Playwright --version 1.50.0

# Install Playwright browsers
dotnet tool install --global Microsoft.Playwright.CLI --version 1.50.0
playwright install firefox

# Create a new console project (if needed)
dotnet new console -n TFLoginAutomation
cd TFLoginAutomation

# Example .csproj file contents

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Playwright" Version="1.50.0" />
  </ItemGroup>
</Project>