← Browser Run / browser-run / features
Human in the Loop
Některé úlohy automatizace prohlížeče vyžadují ruční zásah. Přihlašovací stránka může vyžadovat vícefaktorové ověření, formulář může vyžadovat citlivé přihlašovací údaje, které nechcete předávat automatizačnímu skriptu, nebo může být úloha příliš složitá na to, aby ji šlo plně automatizovat. Funkce Human in the Loop umožňuje, aby člověk vstoupil do živé relace prohlížeče prostřednictvím Live View k vyřešení toho, co automatizace nezvládne, a poté vraťte řízení zpět skriptu.
Případy použití
- Ověřovací postupy: Přihlašovací stránky s MFA, SSO nebo CAPTCHA, které nelze programově obejít
- Zadávání citlivých údajů: Formuláře vyžadující přihlašovací údaje nebo osobní informace, které nechcete předávat automatizačnímu skriptu
- Složité interakce: Jednorázové úlohy, které je příliš složité nebo se nevyplatí plně automatizovat, například konfigurace dashboardu nebo schválení workflow
- Kroky ověření: Potvrzení objednávky, kontrola vygenerovaného obsahu nebo schválení akce před tím, než skript pokračuje
Jak to funguje
Human in the Loop funguje s libovolným Browser Session a poskytuje dva přístupy pro zásah člověka:
Strukturované předání (doporučeno)
Váš skript pomocí příkazů Cloudflare CDP formálně požádá o zásah člověka a počká na jeho dokončení:
- Váš automatizační skript narazí na situaci, která vyžaduje zásah člověka.
- Skript se přihlašuje k odběru
Cloudflare.handoffCompleteudálost. - Skript odesílá
Cloudflare.getLiveViews režimem:taba sdílí vrácenou adresu URL s lidským operátorem. - Skript odesílá
Cloudflare.handoffpříkaz CDP s pokyny pro lidského operátora a poté čeká naCloudflare.handoffComplete. - Operátor otevře URL adresu Live View (/browser-run/features/live-view/), dokončí požadované akce a zvolí možnost „Done“ nebo „Failed“.
Cloudflare.handoffCompletese aktivuje, když člověk označí předání jako dokončené nebo když vyprší časový limit předání.- Automatizace poté pokračuje s informací o tom, zda byl zásah úspěšný.
Viz Příklad: strukturované předání řízení pro kompletní ukázku kódu.
Ruční detekce
U jednodušších případů použití můžete předání řízení spravovat ručně:
- Váš automatizační skript přejde na stránku, která vyžaduje zásah člověka.
- Skript načte Live View URL adresu ze seznamu cílů relace a sdílí ji s lidským operátorem.
- Lidský operátor otevře URL adresu Live View a dokončí požadovanou akci.
- Automatizační skript dokončení rozpozná dotazováním na prvky stránky nebo čekáním na navigační události.
Viz Příklad: ruční detekce pro kompletní ukázku kódu.
Příkazy Cloudflare CDP
Browser Run rozšiřuje standardní Chrome DevTools Protocol (CDP) ↗ s příkazy specifickými pro Cloudflare v rámci Cloudflare.* oboru názvů. Tyto příkazy jsou dostupné pouze při připojení k relaci Browser Run a nabízejí funkce, které standardní specifikace CDP neobsahuje, například vyžádání lidského zásahu, generování Live View URL adres a sledování stavu předání.
Tyto příkazy odesíláte přes relaci CDP stejně jako jakýkoli jiný standardní příkaz CDP. Úplné informace o parametrech a návratových typech najdete v reference protokolu.
Cloudflare.handoff
Vyžádá lidský zásah pro aktuální stránku. Cíl se automaticky určí z relace CDP.
const cdp = await page.createCDPSession();
const { handoffId } = await cdp.send("Cloudflare.handoff", {
// targetId will automatically be resolved from the CDP session
instructions: "Please log in",
timeout: 1800000, // optional, max 30 minutes, if undefined handoff will have no timeout
});Chcete-li požádat o handoff pro konkrétní cíl, pokud váš prohlížeč obsahuje více stránek:
// Get all targets
const { targetInfos } = await cdp.send("Target.getTargets");
// Find a specific target (for example, a page with a specific URL)
const target = targetInfos.find(
(t) => t.type === "page" && t.url.includes("example.com"),
);
if (!target) {
throw new Error("Target not found");
}
// Request handoff for the selected target
const { handoffId } = await cdp.send("Cloudflare.handoff", {
targetId: target.targetId,
instructions: "Please complete the CAPTCHA on this page",
});Cloudflare.handoffComplete událost
Vyvolá se po dokončení lidského zásahu nebo po vypršení časového limitu. Na tuto událost čekejte, abyste mohli obnovit automatizaci, jakmile člověk dokončí svou část.
cdp.once("Cloudflare.handoffComplete", (result) => {
if (result.success) {
console.log("Handoff completed successfully");
} else {
console.log(`Handoff failed: ${result.reason}`);
}
});Cloudflare.getHandoffState
Zjistí, zda je pro aktuální stránku právě aktivní předání.
const state = await cdp.send("Cloudflare.getHandoffState", {
targetId, // optional, defaults to the current page
});
if (state.active) {
console.log(
`Handoff ${state.handoffId} has been active for ${state.durationMs}ms`,
);
}Cloudflare.getLiveView
Vygeneruje Live View URL adresa pro relaci prohlížeče. Použijte ji společně s Cloudflare.handoff k udělení přístupu lidskému operátorovi k živému prohlížeči.
const { devtoolsFrontendUrl } = await cdp.send("Cloudflare.getLiveView", {
targetId, // optional, defaults to the current page
mode: "tab", // optional, one of "tab", "full", or "devtools" (default)
expiresInMs: 300000, // optional, default 5 minutes, max 1 hour
});
console.log(`Live View URL: ${devtoolsFrontendUrl}`);Příklad: strukturované předání řízení
Tento příklad demonstruje strukturovaný postup handoff. Skript pomocí příkazů Cloudflare CDP vyžádá lidský zásah a před pokračováním čeká na událost dokončení:
import puppeteer, { type HandoffCompleteResponse } from "@cloudflare/puppeteer";
const ACCOUNT_ID = "<your-account-id>";
const API_TOKEN = "<your-api-token>";
// Connect to Browser Run
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/browser-run/devtools/browser?keep_alive=600000`,
headers: { Authorization: `Bearer ${API_TOKEN}` },
});
// If using a Cloudflare Worker, you can use: await puppeteer.launch(env.MYBROWSER)
const page = await browser.newPage();
await page.goto("https://github.com/login");
// Create a CDP session to send Browser Run commands
const cdp = await page.createCDPSession();
// Get the Live View URL for the human operator
const { devtoolsFrontendUrl } = await cdp.send("Cloudflare.getLiveView", {
mode: "tab", // required, only "tab" supports handoff
expiresInMs: 300000, // optional, default is 5 minutes
});
// Share the Live View URL with the human operator (for example, send it via Slack, email, or display it in a UI)
console.log(`Human input needed. Open this URL: ${devtoolsFrontendUrl}`);
// Set up completion listener before initiating handoff
const handoffCompletePromise = new Promise<HandoffCompleteResponse>((resolve) => {
cdp.once("Cloudflare.handoffComplete", (event) => resolve(event as HandoffCompleteResponse));
});
// Request human intervention with specific instructions
const { handoffId } = await cdp.send("Cloudflare.handoff", {
instructions: "Please log in with your GitHub credentials",
timeout: 600_000, // 10 minute timeout
});
console.log(`Human intervention requested (ID: ${handoffId})`);
// Wait for human to complete the task
const result = await handoffCompletePromise;
if (result.success) {
console.log("Login successful, continuing automation...");
// Continue with your automation...
await page.goto("https://github.com/settings/profile");
} else {
console.error(`Human intervention failed: ${result.reason}`);
}
await browser.close();import type { HandoffCompleteResponse } from "@cloudflare/playwright";
import { chromium } from "playwright-core";
// If using a Cloudflare Worker, import { launch } from "@cloudflare/playwright" and use launch(env.MYBROWSER) instead.
const ACCOUNT_ID = "<your-account-id>";
const API_TOKEN = "<your-api-token>";
// Connect to Browser Run
const browser = await chromium.connectOverCDP(
`wss://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/browser-run/devtools/browser?keep_alive=600000`,
{ headers: { Authorization: `Bearer ${API_TOKEN}` } },
);
const context = browser.contexts()[0] ?? (await browser.newContext());
const page = await context.newPage();
await page.goto("https://github.com/login");
// Create a CDP session to send Browser Run commands
const cdp = await context.newCDPSession(page);
// Get the Live View URL for the human operator
const { devtoolsFrontendUrl } = await cdp.send("Cloudflare.getLiveView", {
mode: "tab", // required, only "tab" supports handoff
expiresInMs: 300000, // optional, default is 5 minutes
});
// Share the Live View URL with the human operator (for example, send it via Slack, email, or display it in a UI)
console.log(`Human input needed. Open this URL: ${devtoolsFrontendUrl}`);
// Set up completion listener before initiating handoff.
// `cdp.once` for Cloudflare.* events needs a call-site cast because
// Playwright's `CDPSession.on/once/off` are declared as arrow-property
// signatures that TypeScript can't merge overloads into.
const handoffCompletePromise = new Promise<HandoffCompleteResponse>((resolve) => {
(cdp.once as (event: string, listener: (p: HandoffCompleteResponse) => void) => void)(
"Cloudflare.handoffComplete",
resolve,
);
});
// Request human intervention with specific instructions
const { handoffId } = await cdp.send("Cloudflare.handoff", {
instructions: "Please log in with your GitHub credentials",
timeout: 600000, // 10 minute timeout
});
console.log(`Human intervention requested (ID: ${handoffId})`);
// Wait for human to complete the task
const result = await handoffCompletePromise;
if (result.success) {
console.log("Login successful, continuing automation...");
// Continue with your automation...
await page.goto("https://github.com/settings/profile");
} else {
console.error(`Human intervention failed: ${result.reason}`);
}
await browser.close();Příklad: ruční detekce
Tento příklad používá manuální přístup, kdy se sdílí URL adresa Live View a opakovaně se kontroluje dokončení:
import puppeteer from "@cloudflare/puppeteer";
const ACCOUNT_ID = "<your-account-id>";
const API_TOKEN = "<your-api-token>";
// Connect to Browser Run
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/browser-run/devtools/browser?keep_alive=600000`,
headers: { Authorization: `Bearer ${API_TOKEN}` },
});
const page = await browser.newPage();
await page.goto("https://github.com/login");
// Create CDP session and get Live View URL
const cdp = await page.createCDPSession();
const { devtoolsFrontendUrl } = await cdp.send("Cloudflare.getLiveView", {
expiresInMs: 300000, // 5 minutes
});
// Share the Live View URL with the human operator (for example, send it via Slack, email, or display it in a UI)
console.log(`Human input needed. Open this URL: ${devtoolsFrontendUrl}`);
// Wait until GitHub reports a non-empty `<meta name="user-login">` in the
// page head. This meta tag is always present, but its content is empty
// for logged-out viewers and set to the username after login. Waiting for
// content to be populated is a reliable "human finished logging in" signal.
await page.waitForFunction(
() =>
document
.querySelector('meta[name="user-login"]')
?.getAttribute("content") !== "",
{ timeout: 300000 },
);
// Login complete, continue automation
console.log("Login complete. Continuing automation...");
// Verify we're logged in and read the username
const username = await page.$eval('meta[name="user-login"]', (el) =>
el.getAttribute("content"),
);
console.log(`Logged in as: ${username}`);
await page.goto("https://github.com/");
await browser.close();import type { GetLiveViewResponse } from "@cloudflare/playwright";
import { chromium } from "playwright-core";
// If using a Cloudflare Worker, import { launch } from "@cloudflare/playwright" and use launch(env.MYBROWSER) instead.
const ACCOUNT_ID = "<your-account-id>";
const API_TOKEN = "<your-api-token>";
// Connect to Browser Run
const browser = await chromium.connectOverCDP(
`wss://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/browser-run/devtools/browser?keep_alive=600000`,
{ headers: { Authorization: `Bearer ${API_TOKEN}` } },
);
const context = browser.contexts()[0] ?? (await browser.newContext());
const page = await context.newPage();
await page.goto("https://github.com/login");
// Create CDP session and get Live View URL
const cdp = await context.newCDPSession(page);
const { devtoolsFrontendUrl }: GetLiveViewResponse = await cdp.send(
"Cloudflare.getLiveView",
{ expiresInMs: 300000 }, // 5 minutes
);
// Share the Live View URL with the human operator (for example, send it via Slack, email, or display it in a UI)
console.log(`Human input needed. Open this URL: ${devtoolsFrontendUrl}`);
// Wait until GitHub reports a non-empty `<meta name="user-login">` in the
// page head. This meta tag is always present, but its content is empty
// for logged-out viewers and set to the username after login. Waiting for
// content to be populated is a reliable "human finished logging in" signal.
await page.waitForFunction(
() =>
document
.querySelector('meta[name="user-login"]')
?.getAttribute("content") !== "",
null,
{ timeout: 300000 },
);
// Login complete, continue automation
console.log("Login complete. Continuing automation...");
// Verify we're logged in and read the username
const username = await page.$eval('meta[name="user-login"]', (el) =>
el.getAttribute("content"),
);
console.log(`Logged in as: ${username}`);
await page.goto("https://github.com/");
await browser.close();Osvědčené postupy
Uveďte jasné pokyny
Poskytněte lidským operátorům konkrétní, prakticky uplatnitelné pokyny:
// Good: specific and actionable
await cdp.send("Cloudflare.handoff", {
instructions:
"Review the items in the cart and if approved for checkout, click 'Done'. Otherwise, click 'Failed' and provide a reason.",
});Nastavení vhodných časových limitů
Přizpůsobte délku timeoutu složitosti úlohy:
// Quick tasks — 2-3 minutes
await cdp.send("Cloudflare.handoff", {
instructions: "Click the 'I agree' checkbox and submit",
timeout: 120000,
});
// Complex tasks - 10-15 minutes
await cdp.send("Cloudflare.handoff", {
instructions: "Complete the multi-page application form with test data",
timeout: 900000,
});Sledování stavu předání
V případě potřeby zkontrolujte stav předání:
// Check if a handoff is already active before requesting a new one
const currentState = await cdp.send("Cloudflare.getHandoffState");
if (currentState.active) {
console.log(`Handoff already active: ${currentState.handoffId}`);
// Wait for current handoff or handle appropriately
} else {
// Safe to start new handoff
await cdp.send("Cloudflare.handoff", {
/* ... */
});
}