{"id":872,"date":"2026-05-08T08:14:49","date_gmt":"2026-05-08T00:14:49","guid":{"rendered":"https:\/\/connectword.dpdns.org\/?p=872"},"modified":"2026-05-08T08:14:49","modified_gmt":"2026-05-08T00:14:49","slug":"build-a-cloakbrowser-automation-workflow-with-stealth-chromium-persistent-profiles-and-browser-signal-inspection","status":"publish","type":"post","link":"https:\/\/connectword.dpdns.org\/?p=872","title":{"rendered":"Build a CloakBrowser Automation Workflow with Stealth Chromium, Persistent Profiles, and Browser Signal Inspection"},"content":{"rendered":"<p>In this tutorial, we explore <a href=\"https:\/\/github.com\/CloakHQ\/CloakBrowser\"><strong>CloakBrowser<\/strong><\/a>, a Python-friendly browser automation tool that uses Playwright-style APIs within a stealth Chromium environment. We begin by setting up CloakBrowser, preparing the required browser binary, and resolving the common Colab asyncio loop issue by running the sync browser workflow in a separate worker thread. We then move through practical automation steps, including launching a browser, creating customized browser contexts, inspecting browser-visible signals, interacting with a local test page, saving session state, restoring localStorage, using persistent browser profiles, capturing screenshots, and extracting rendered page content for parsing.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">import os\nimport sys\nimport json\nimport time\nimport shutil\nimport base64\nimport subprocess\nimport concurrent.futures\nfrom pathlib import Path\nfrom datetime import datetime\nfrom textwrap import dedent\n\n\ndef run_cmd(cmd, check=True, capture=False):\n   print(f\"n$ {' '.join(cmd)}\")\n   result = subprocess.run(\n       cmd,\n       check=check,\n       text=True,\n       stdout=subprocess.PIPE if capture else None,\n       stderr=subprocess.STDOUT if capture else None,\n   )\n   if capture and result.stdout:\n       print(result.stdout[:4000])\n   return result\n\n\n\n\nprint(\"Installing CloakBrowser and helper packages...\")\nrun_cmd([\n   sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"-U\",\n   \"cloakbrowser\", \"playwright\", \"pandas\", \"beautifulsoup4\"\n])\n\n\nprint(\"nInstalling Chromium runtime dependencies for Colab...\")\ntry:\n   run_cmd([sys.executable, \"-m\", \"playwright\", \"install-deps\", \"chromium\"], check=False)\nexcept Exception as e:\n   print(\"Dependency installer warning:\", repr(e))\n\n\n\n\nfrom cloakbrowser import (\n   launch,\n   launch_context,\n   launch_persistent_context,\n   ensure_binary,\n   binary_info,\n)\n\n\nimport pandas as pd\nfrom bs4 import BeautifulSoup\nfrom IPython.display import display, Image\n\n\n\n\nWORKDIR = Path(\"\/content\/cloakbrowser_advanced_tutorial\")\nWORKDIR.mkdir(parents=True, exist_ok=True)\n\n\nSCREENSHOT_PATH = WORKDIR \/ \"cloakbrowser_result.png\"\nSTORAGE_STATE_PATH = WORKDIR \/ \"storage_state.json\"\nPROFILE_DIR = WORKDIR \/ \"persistent_profile\"\n\n\n\n\nprint(\"nPreparing CloakBrowser binary...\")\n\n\ntry:\n   ensure_binary()\nexcept Exception as e:\n   print(\"Binary setup warning:\", repr(e))\n\n\nprint(\"nCloakBrowser binary info:\")\n\n\ntry:\n   info = binary_info()\n   print(json.dumps(info, indent=2, default=str))\nexcept Exception as e:\n   print(\"Could not read binary info:\", repr(e))<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We start by installing CloakBrowser, Playwright, pandas, and BeautifulSoup so the Colab environment has everything needed for browser automation and result analysis. We also install Chromium runtime dependencies, import the main CloakBrowser launch utilities, and define the working paths for screenshots, storage state, and persistent profiles. We then prepare the CloakBrowser binary and print its details to confirm the browser engine is installed correctly before running automation.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">def make_data_url(html: str) -&gt; str:\n   encoded = base64.b64encode(html.encode(\"utf-8\")).decode(\"ascii\")\n   return f\"data:text\/html;base64,{encoded}\"\n\n\n\n\ndef print_section(title):\n   print(\"n\" + \"=\" * 80)\n   print(title)\n   print(\"=\" * 80)\n\n\n\n\ndef safe_close(obj, label=\"object\"):\n   try:\n       if obj:\n           obj.close()\n   except Exception as e:\n       print(f\"Warning while closing {label}: {e}\")\n\n\n\n\ndef run_sync_browser_job_in_thread(fn, *args, **kwargs):\n   \"\"\"\n   Google Colab and Jupyter already run an asyncio event loop.\n\n\n   CloakBrowser currently exposes Playwright-style sync helpers such as:\n     - launch()\n     - launch_context()\n     - launch_persistent_context()\n\n\n   Playwright's sync API cannot run inside an already-running event loop.\n   Therefore, we run the entire browser automation job inside a separate thread.\n   \"\"\"\n   with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n       future = executor.submit(fn, *args, **kwargs)\n       return future.result()\n\n\n\n\ntest_page_html = dedent(\"\"\"\n&lt;!doctype html&gt;\n&lt;html&gt;\n&lt;head&gt;\n &lt;meta charset=\"utf-8\"&gt;\n &lt;title&gt;CloakBrowser Local Automation Lab&lt;\/title&gt;\n &lt;style&gt;\n   body {\n     font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n     max-width: 900px;\n     margin: 40px auto;\n     padding: 24px;\n     line-height: 1.5;\n     background: #f7f7f7;\n     color: #222;\n   }\n   .card {\n     background: white;\n     border-radius: 18px;\n     padding: 24px;\n     box-shadow: 0 8px 30px rgba(0,0,0,0.08);\n     margin-bottom: 18px;\n   }\n   label {\n     display: block;\n     margin-top: 12px;\n     font-weight: 600;\n   }\n   input, textarea, button {\n     width: 100%;\n     box-sizing: border-box;\n     padding: 12px;\n     margin-top: 8px;\n     border: 1px solid #ccc;\n     border-radius: 12px;\n     font-size: 15px;\n   }\n   button {\n     cursor: pointer;\n     background: #111;\n     color: white;\n     font-weight: 700;\n   }\n   pre {\n     background: #111;\n     color: #00ff99;\n     padding: 16px;\n     overflow-x: auto;\n     border-radius: 12px;\n   }\n &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n &lt;div class=\"card\"&gt;\n   &lt;h1&gt;CloakBrowser Local Automation Lab&lt;\/h1&gt;\n   &lt;p&gt;\n     This page runs locally from a data URL. We use it to inspect browser-visible\n     properties and demonstrate Playwright-style interaction safely.\n   &lt;\/p&gt;\n &lt;\/div&gt;\n\n\n &lt;div class=\"card\"&gt;\n   &lt;h2&gt;Interaction Form&lt;\/h2&gt;\n\n\n   &lt;label&gt;Name&lt;\/label&gt;\n   &lt;input id=\"name\" placeholder=\"Type your name here\"&gt;\n\n\n   &lt;label&gt;Message&lt;\/label&gt;\n   &lt;textarea id=\"message\" rows=\"4\" placeholder=\"Type a short message\"&gt;&lt;\/textarea&gt;\n\n\n   &lt;button id=\"submit\"&gt;Submit Local Form&lt;\/button&gt;\n   &lt;p id=\"status\"&gt;Waiting for interaction...&lt;\/p&gt;\n &lt;\/div&gt;\n\n\n &lt;div class=\"card\"&gt;\n   &lt;h2&gt;Browser Signals&lt;\/h2&gt;\n   &lt;pre id=\"signals\"&gt;&lt;\/pre&gt;\n &lt;\/div&gt;\n\n\n &lt;script&gt;\n   async function collectSignals() {\n     const canvas = document.createElement(\"canvas\");\n     const gl = canvas.getContext(\"webgl\") || canvas.getContext(\"experimental-webgl\");\n\n\n     let webglVendor = null;\n     let webglRenderer = null;\n\n\n     if (gl) {\n       const debugInfo = gl.getExtension(\"WEBGL_debug_renderer_info\");\n       if (debugInfo) {\n         webglVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);\n         webglRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);\n       }\n     }\n\n\n     const signals = {\n       title: document.title,\n       userAgent: navigator.userAgent,\n       webdriver: navigator.webdriver,\n       platform: navigator.platform,\n       languages: navigator.languages,\n       language: navigator.language,\n       hardwareConcurrency: navigator.hardwareConcurrency,\n       deviceMemory: navigator.deviceMemory || null,\n       pluginsLength: navigator.plugins ? navigator.plugins.length : null,\n       chromeObjectPresent: typeof window.chrome === \"object\",\n       timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n       screen: {\n         width: screen.width,\n         height: screen.height,\n         colorDepth: screen.colorDepth,\n         pixelDepth: screen.pixelDepth\n       },\n       viewport: {\n         innerWidth: window.innerWidth,\n         innerHeight: window.innerHeight,\n         devicePixelRatio: window.devicePixelRatio\n       },\n       webglVendor,\n       webglRenderer,\n       localStorageWorks: (() =&gt; {\n         try {\n           localStorage.setItem(\"cloakbrowser_test\", \"ok\");\n           return localStorage.getItem(\"cloakbrowser_test\") === \"ok\";\n         } catch (e) {\n           return false;\n         }\n       })()\n     };\n\n\n     document.getElementById(\"signals\").textContent = JSON.stringify(signals, null, 2);\n     return signals;\n   }\n\n\n   document.getElementById(\"submit\").addEventListener(\"click\", () =&gt; {\n     const name = document.getElementById(\"name\").value;\n     const message = document.getElementById(\"message\").value;\n\n\n     localStorage.setItem(\"tutorial_name\", name);\n     localStorage.setItem(\"tutorial_message\", message);\n\n\n     document.getElementById(\"status\").textContent =\n       `Saved locally for ${name}: ${message}`;\n   });\n\n\n   collectSignals();\n &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n\"\"\").strip()\n\n\nTEST_PAGE_URL = make_data_url(test_page_html)<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We define helper functions for creating data URLs, printing section headers, safely closing browser objects, and running synchronous browser jobs inside a separate thread. We use the thread wrapper because Google Colab already runs an asyncio loop, and this prevents Playwright\u2019s sync API from failing. We also create a safe local HTML test page that collects browser-visible signals, supports form interaction, and stores test values in localStorage.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">def cloakbrowser_tutorial_job():\n   results = {\n       \"basic_launch\": None,\n       \"advanced_context\": None,\n       \"storage_restore\": None,\n       \"persistent_profile\": None,\n       \"rendered_extraction\": None,\n       \"static_parsing\": None,\n       \"errors\": [],\n   }\n\n\n   print_section(\"1. Basic CloakBrowser launch\")\n\n\n   browser = None\n\n\n   try:\n       browser = launch(\n           headless=True,\n           humanize=True,\n           args=[\n               \"--no-sandbox\",\n               \"--disable-dev-shm-usage\",\n           ],\n       )\n\n\n       page = browser.new_page()\n       page.goto(\"https:\/\/example.com\", wait_until=\"domcontentloaded\", timeout=60000)\n\n\n       results[\"basic_launch\"] = {\n           \"title\": page.title(),\n           \"body_preview\": page.locator(\"body\").inner_text(timeout=15000)[:300],\n           \"url\": page.url,\n       }\n\n\n       print(json.dumps(results[\"basic_launch\"], indent=2))\n\n\n   except Exception as e:\n       error = {\n           \"section\": \"basic_launch\",\n           \"error\": repr(e),\n       }\n       results[\"errors\"].append(error)\n       print(error)\n\n\n   finally:\n       safe_close(browser, \"basic browser\")\n\n\n   print_section(\"2. Advanced context launch with custom browser context\")\n\n\n   context = None\n\n\n   try:\n       context = launch_context(\n           headless=True,\n           humanize=True,\n           viewport={\"width\": 1365, \"height\": 768},\n           locale=\"en-US\",\n           timezone_id=\"America\/New_York\",\n           color_scheme=\"light\",\n           extra_http_headers={\n               \"Accept-Language\": \"en-US,en;q=0.9\",\n               \"X-Tutorial-Run\": \"cloakbrowser-colab\",\n           },\n           args=[\n               \"--no-sandbox\",\n               \"--disable-dev-shm-usage\",\n           ],\n       )\n\n\n       page = context.new_page()\n       page.goto(TEST_PAGE_URL, wait_until=\"domcontentloaded\", timeout=60000)\n\n\n       page.locator(\"#name\").fill(\"CloakBrowser Colab User\")\n       page.locator(\"#message\").fill(\n           \"We are testing safe local browser automation in Google Colab.\"\n       )\n       page.locator(\"#submit\").click()\n\n\n       page.wait_for_timeout(1000)\n\n\n       signals = page.evaluate(\"() =&gt; collectSignals()\")\n       status_text = page.locator(\"#status\").inner_text()\n\n\n       page.screenshot(path=str(SCREENSHOT_PATH), full_page=True)\n       context.storage_state(path=str(STORAGE_STATE_PATH))\n\n\n       results[\"advanced_context\"] = {\n           \"status_text\": status_text,\n           \"signals\": signals,\n           \"screenshot_path\": str(SCREENSHOT_PATH),\n           \"storage_state_path\": str(STORAGE_STATE_PATH),\n       }\n\n\n       print(json.dumps(results[\"advanced_context\"], indent=2, default=str))\n\n\n   except Exception as e:\n       error = {\n           \"section\": \"advanced_context\",\n           \"error\": repr(e),\n       }\n       results[\"errors\"].append(error)\n       print(error)\n\n\n   finally:\n       safe_close(context, \"advanced context\")\n\n\n   print_section(\"3. Restore localStorage using storage_state\")\n\n\n   restored_context = None\n\n\n   try:\n       restored_context = launch_context(\n           headless=True,\n           humanize=True,\n           storage_state=str(STORAGE_STATE_PATH),\n           viewport={\"width\": 1365, \"height\": 768},\n           locale=\"en-US\",\n           timezone_id=\"America\/New_York\",\n           args=[\n               \"--no-sandbox\",\n               \"--disable-dev-shm-usage\",\n           ],\n       )\n\n\n       restored_page = restored_context.new_page()\n       restored_page.goto(TEST_PAGE_URL, wait_until=\"domcontentloaded\", timeout=60000)\n\n\n       restored_values = restored_page.evaluate(\"\"\"\n       () =&gt; ({\n         tutorial_name: localStorage.getItem(\"tutorial_name\"),\n         tutorial_message: localStorage.getItem(\"tutorial_message\"),\n         cloakbrowser_test: localStorage.getItem(\"cloakbrowser_test\")\n       })\n       \"\"\")\n\n\n       results[\"storage_restore\"] = restored_values\n\n\n       print(json.dumps(restored_values, indent=2))\n\n\n   except Exception as e:\n       error = {\n           \"section\": \"storage_restore\",\n           \"error\": repr(e),\n       }\n       results[\"errors\"].append(error)\n       print(error)\n\n\n   finally:\n       safe_close(restored_context, \"restored context\")<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We define the main tutorial job and begin by launching CloakBrowser in headless mode to open a simple public page and extract its title, body preview, and URL. We then create a customized browser context with viewport, locale, timezone, color scheme, and custom headers to simulate a more controlled browser session. We interact with the local test form, collect browser signals, save a screenshot, store session state, and then test whether localStorage can be restored in a fresh context.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">   print_section(\"4. Persistent profile demonstration\")\n\n\n   if PROFILE_DIR.exists():\n       shutil.rmtree(PROFILE_DIR)\n\n\n   try:\n       ctx1 = launch_persistent_context(\n           str(PROFILE_DIR),\n           headless=True,\n           humanize=True,\n           viewport={\"width\": 1280, \"height\": 720},\n           locale=\"en-US\",\n           timezone_id=\"America\/New_York\",\n           args=[\n               \"--no-sandbox\",\n               \"--disable-dev-shm-usage\",\n           ],\n       )\n\n\n       p1 = ctx1.new_page()\n       p1.goto(TEST_PAGE_URL, wait_until=\"domcontentloaded\", timeout=60000)\n\n\n       p1.evaluate(\"\"\"\n       () =&gt; {\n         localStorage.setItem(\"persistent_profile_demo\", \"saved_across_browser_restarts\");\n         localStorage.setItem(\"persistent_profile_timestamp\", new Date().toISOString());\n       }\n       \"\"\")\n\n\n       first_value = p1.evaluate(\"() =&gt; localStorage.getItem('persistent_profile_demo')\")\n       ctx1.close()\n\n\n       ctx2 = launch_persistent_context(\n           str(PROFILE_DIR),\n           headless=True,\n           humanize=True,\n           viewport={\"width\": 1280, \"height\": 720},\n           locale=\"en-US\",\n           timezone_id=\"America\/New_York\",\n           args=[\n               \"--no-sandbox\",\n               \"--disable-dev-shm-usage\",\n           ],\n       )\n\n\n       p2 = ctx2.new_page()\n       p2.goto(TEST_PAGE_URL, wait_until=\"domcontentloaded\", timeout=60000)\n\n\n       second_value = p2.evaluate(\n           \"() =&gt; localStorage.getItem('persistent_profile_demo')\"\n       )\n       second_timestamp = p2.evaluate(\n           \"() =&gt; localStorage.getItem('persistent_profile_timestamp')\"\n       )\n\n\n       ctx2.close()\n\n\n       persistent_results = {\n           \"first_run_value\": first_value,\n           \"second_run_value\": second_value,\n           \"second_run_timestamp\": second_timestamp,\n           \"profile_dir\": str(PROFILE_DIR),\n           \"persisted_successfully\": first_value == second_value and second_value is not None,\n       }\n\n\n       results[\"persistent_profile\"] = persistent_results\n\n\n       print(json.dumps(persistent_results, indent=2))\n\n\n   except Exception as e:\n       error = {\n           \"section\": \"persistent_profile\",\n           \"error\": repr(e),\n       }\n       results[\"errors\"].append(error)\n       print(error)\n\n\n   print_section(\"5. Browser-rendered extraction plus static parsing\")\n\n\n   browser = None\n\n\n   try:\n       browser = launch(\n           headless=True,\n           humanize=True,\n           args=[\n               \"--no-sandbox\",\n               \"--disable-dev-shm-usage\",\n           ],\n       )\n\n\n       page = browser.new_page()\n       page.goto(\"https:\/\/example.com\", wait_until=\"domcontentloaded\", timeout=60000)\n\n\n       rendered = {\n           \"title\": page.title(),\n           \"h1\": page.locator(\"h1\").inner_text(timeout=15000),\n           \"paragraph\": page.locator(\"p\").first.inner_text(timeout=15000),\n           \"url\": page.url,\n           \"captured_at\": datetime.utcnow().isoformat() + \"Z\",\n       }\n\n\n       html = page.content()\n       soup = BeautifulSoup(html, \"html.parser\")\n\n\n       parsed = {\n           \"static_title\": soup.title.get_text(strip=True) if soup.title else None,\n           \"static_h1\": soup.find(\"h1\").get_text(strip=True) if soup.find(\"h1\") else None,\n           \"links\": [\n               {\n                   \"text\": a.get_text(strip=True),\n                   \"href\": a.get(\"href\"),\n               }\n               for a in soup.find_all(\"a\")\n           ],\n       }\n\n\n       results[\"rendered_extraction\"] = rendered\n       results[\"static_parsing\"] = parsed\n\n\n       print(\"Rendered extraction:\")\n       print(json.dumps(rendered, indent=2))\n\n\n       print(\"nStatic parsing:\")\n       print(json.dumps(parsed, indent=2))\n\n\n   except Exception as e:\n       error = {\n           \"section\": \"rendered_extraction\",\n           \"error\": repr(e),\n       }\n       results[\"errors\"].append(error)\n       print(error)\n\n\n   finally:\n       safe_close(browser, \"extraction browser\")\n\n\n   return results<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We continue the same tutorial job by creating a persistent browser profile directory and using it to save localStorage across browser restarts. We launch the persistent profile twice: first to write a stored value, and then to confirm that the value persists in the second launch. We also demonstrated rendering a live page and extracting the rendered page, then parsing the browser-returned HTML with BeautifulSoup for static content analysis.<\/p>\n<div class=\"dm-code-snippet dark dm-normal-version default no-background-mobile\">\n<div class=\"control-language\">\n<div class=\"dm-buttons\">\n<div class=\"dm-buttons-left\">\n<div class=\"dm-button-snippet red-button\"><\/div>\n<div class=\"dm-button-snippet orange-button\"><\/div>\n<div class=\"dm-button-snippet green-button\"><\/div>\n<\/div>\n<div class=\"dm-buttons-right\"><a><span class=\"dm-copy-text\">Copy Code<\/span><span class=\"dm-copy-confirmed\">Copied<\/span><span class=\"dm-error-message\">Use a different Browser<\/span><\/a><\/div>\n<\/div>\n<pre class=\" no-line-numbers\"><code class=\" no-wrap language-php\">tutorial_results = run_sync_browser_job_in_thread(cloakbrowser_tutorial_job)\n\n\n\n\nprint_section(\"6. Final tutorial summary\")\n\n\nif SCREENSHOT_PATH.exists():\n   display(Image(filename=str(SCREENSHOT_PATH)))\n\n\nrows = []\n\n\nadvanced = tutorial_results.get(\"advanced_context\")\n\n\nif advanced and advanced.get(\"signals\"):\n   signals = advanced[\"signals\"]\n\n\n   rows.extend([\n       {\n           \"category\": \"Browser signal\",\n           \"metric\": \"navigator.webdriver\",\n           \"value\": signals.get(\"webdriver\"),\n       },\n       {\n           \"category\": \"Browser signal\",\n           \"metric\": \"navigator.plugins.length\",\n           \"value\": signals.get(\"pluginsLength\"),\n       },\n       {\n           \"category\": \"Browser signal\",\n           \"metric\": \"window.chrome present\",\n           \"value\": signals.get(\"chromeObjectPresent\"),\n       },\n       {\n           \"category\": \"Browser signal\",\n           \"metric\": \"timezone\",\n           \"value\": signals.get(\"timezone\"),\n       },\n       {\n           \"category\": \"Browser signal\",\n           \"metric\": \"platform\",\n           \"value\": signals.get(\"platform\"),\n       },\n       {\n           \"category\": \"Browser signal\",\n           \"metric\": \"viewport\",\n           \"value\": json.dumps(signals.get(\"viewport\")),\n       },\n       {\n           \"category\": \"Browser signal\",\n           \"metric\": \"webglRenderer\",\n           \"value\": signals.get(\"webglRenderer\"),\n       },\n   ])\n\n\npersistent = tutorial_results.get(\"persistent_profile\")\n\n\nif persistent:\n   rows.append({\n       \"category\": \"Persistence\",\n       \"metric\": \"persistent profile restored localStorage\",\n       \"value\": persistent.get(\"persisted_successfully\"),\n   })\n\n\nbasic = tutorial_results.get(\"basic_launch\")\n\n\nif basic:\n   rows.append({\n       \"category\": \"Navigation\",\n       \"metric\": \"basic launch title\",\n       \"value\": basic.get(\"title\"),\n   })\n\n\nrendered = tutorial_results.get(\"rendered_extraction\")\n\n\nif rendered:\n   rows.append({\n       \"category\": \"Extraction\",\n       \"metric\": \"rendered h1\",\n       \"value\": rendered.get(\"h1\"),\n   })\n\n\nrows.extend([\n   {\n       \"category\": \"Output\",\n       \"metric\": \"screenshot_path\",\n       \"value\": str(SCREENSHOT_PATH) if SCREENSHOT_PATH.exists() else None,\n   },\n   {\n       \"category\": \"Output\",\n       \"metric\": \"storage_state_path\",\n       \"value\": str(STORAGE_STATE_PATH) if STORAGE_STATE_PATH.exists() else None,\n   },\n   {\n       \"category\": \"Output\",\n       \"metric\": \"working_directory\",\n       \"value\": str(WORKDIR),\n   },\n])\n\n\nif tutorial_results.get(\"errors\"):\n   for err in tutorial_results[\"errors\"]:\n       rows.append({\n           \"category\": \"Error\",\n           \"metric\": err.get(\"section\"),\n           \"value\": err.get(\"error\"),\n       })\n\n\nsummary_df = pd.DataFrame(rows)\ndisplay(summary_df)\n\n\nprint(\"nTutorial complete.\")\nprint(\"Files created:\")\n\n\nfor path in sorted(WORKDIR.glob(\"*\")):\n   print(\" -\", path)<\/code><\/pre>\n<\/div>\n<\/div>\n<p>We run the complete CloakBrowser tutorial job inside the safe thread wrapper. We display the captured screenshot and build a structured pandas summary table containing browser signals, persistence results, navigation output, extraction output, generated file paths, and any errors. We finish by printing the tutorial completion message and listing all files created in the working directory.<\/p>\n<p>In conclusion, we built a complete Colab-ready CloakBrowser workflow that demonstrates both basic and advanced browser-automation patterns in a safe, controlled environment. We used local test pages and simple public pages to understand how CloakBrowser handles browser contexts, storage, persistence, screenshots, and the extraction of rendered HTML. We also made the tutorial reliable for notebook environments by isolating the sync browser execution from Colab\u2019s active event loop. This provides a strong foundation for building more advanced, responsible browser-automation pipelines with CloakBrowser and Python.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<p>Check out\u00a0the\u00a0<strong><a href=\"https:\/\/github.com\/Marktechpost\/AI-Agents-Projects-Tutorials\/blob\/main\/AI%20Agents%20Codes\/cloakbrowser_colab_browser_automation_tutorial_Marktechpost.ipynb\" target=\"_blank\" rel=\"noreferrer noopener\">Full Codes here<\/a><\/strong>.<strong>\u00a0<\/strong>Also,\u00a0feel free to follow us on\u00a0<strong><a href=\"https:\/\/x.com\/intent\/follow?screen_name=marktechpost\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Twitter<\/mark><\/a><\/strong>\u00a0and don\u2019t forget to join our\u00a0<strong><a href=\"https:\/\/www.reddit.com\/r\/machinelearningnews\/\" target=\"_blank\" rel=\"noreferrer noopener\">150k+ ML SubReddit<\/a><\/strong>\u00a0and Subscribe to\u00a0<strong><a href=\"https:\/\/www.aidevsignals.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">our Newsletter<\/a><\/strong>. Wait! are you on telegram?\u00a0<strong><a href=\"https:\/\/t.me\/machinelearningresearchnews\" target=\"_blank\" rel=\"noreferrer noopener\">now you can join us on telegram as well.<\/a><\/strong><\/p>\n<p>Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.?\u00a0<strong><a href=\"https:\/\/forms.gle\/MTNLpmJtsFA3VRVd9\" target=\"_blank\" rel=\"noreferrer noopener\"><mark>Connect with us<\/mark><\/a><\/strong><\/p>\n<p>The post <a href=\"https:\/\/www.marktechpost.com\/2026\/05\/07\/build-a-cloakbrowser-automation-workflow-with-stealth-chromium-persistent-profiles-and-browser-signal-inspection\/\">Build a CloakBrowser Automation Workflow with Stealth Chromium, Persistent Profiles, and Browser Signal Inspection<\/a> appeared first on <a href=\"https:\/\/www.marktechpost.com\/\">MarkTechPost<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this tutorial, we explore C&hellip;<\/p>\n","protected":false},"author":1,"featured_media":29,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-872","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts\/872","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=872"}],"version-history":[{"count":0,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/posts\/872\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=\/wp\/v2\/media\/29"}],"wp:attachment":[{"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=872"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=872"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/connectword.dpdns.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=872"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}