xiaohongshu
xiaohongshu
automation-best-practices
Best practices for writing reliable browser automation scripts with Intuned. Use when writing, reviewing, or editing automation code, scraping scripts, or browser-based workflows. Provides guidance on selectors, waiting strategies, anti-detection, performance optimization, and SDK patterns.
Full skill instructions
This skill provides comprehensive guidance for writing reliable, efficient, and maintainable browser automation scripts using the Intuned platform and SDK.
Reference: docs/03-how-to/solve/make-automations-faster.mdx
Never use arbitrary delays:
// Bad - wastes time or fails unpredictably
await page.waitForTimeout(5000);
// Good - waits for specific conditions
await page.locator("#data-table").waitFor({ state: "visible" });
Use DOM and network settling helpers:
// After actions that modify the DOM
await waitForDomSettled({
source: page,
settleDurationMs: 500,
timeoutInMs: 30000
});
// After navigation or API calls
await withNetworkSettledWait(async (page) => {
await page.click("button.load-data");
}, { page, timeoutInMs: 30000 });
Set shorter timeouts for fast pages:
page.setDefaultTimeout(10000); // 10 seconds instead of 30
Reference: docs/01-learn/recipes/network-interception.mdx
Intercept API responses directly instead of parsing the DOM (often 10x faster):
let apiData: any;
page.on("response", async (response) => {
if (response.url().includes("/api/products")) {
apiData = await response.json();
}
});
await withNetworkSettledWait(async (page) => {
await page.click("button.load-products");
}, { page });
return apiData;
Avoid iterating with locators - each locator auto-waits, adding overhead per item:
// Bad - multiple round trips
const items = await page.locator(".product-card").all();
for (const item of items) {
const name = await item.locator(".name").textContent();
}
// Good - wait once, extract all in single evaluate
await page.locator(".product-card").first().waitFor({ state: "visible" });
const products = await page.evaluate(() => {
return Array.from(document.querySelectorAll(".product-card")).map(card => ({
name: card.querySelector(".name")?.textContent?.trim(),
price: card.querySelector(".price")?.textContent?.trim()
}));
});
// Slow - types character by character
await page.locator("input").pressSequentially("[email protected]");
// Fast - instant input
await page.locator("input").fill("[email protected]");
// Slow - clicking through filters
await page.click("[data-category='electronics']");
// Fast - navigate directly with query params
await page.goto("https://example.com/products?category=electronics");
Reference: docs/03-how-to/solve/optimize-cost.mdx
Move expensive operations outside the browser context:
// Bad - heavy processing in browser
const result = await page.evaluate(() => {
return processData(document.body.innerHTML);
});
// Good - extract raw data, process afterward
const rawData = await page.evaluate(() => document.body.innerHTML);
const result = processData(rawData);
Reference: docs/02-features/stealth-mode-captcha-solving-proxies.mdx
Start simple and add features only as needed:
| Blocking type | Solution |
|---|---|
| IP-based blocking | Proxies |
| Headless detection | Headful mode |
| Fingerprint/automation detection | Stealth mode + Headful |
| CAPTCHAs | CAPTCHA solving (requires Headful) |
Enable for sites with fingerprint detection:
{
"browserMode": "headful"
}
Required for CAPTCHA solving and better browser fingerprints.
Patches automation framework leaks:
{
"stealthMode": true
}
Notes: Requires Playwright v1.55+, works best with headful mode, platform only.
Supported types: Google reCAPTCHA v2, hCaptcha, AWS CAPTCHA, Cloudflare managed challenge, GeeTest, Lemin, custom image/text CAPTCHAs.
Reference: docs/01-learn/recipes/pagination.mdx
async function scrapeAllPages(page: Page) {
const allProducts: Product[] = [];
let currentPage = 1;
const maxPages = 100;
while (currentPage <= maxPages) {
const products = await extractProducts(page);
allProducts.push(...products);
const nextButton = page.locator("button.next-page");
if (!(await nextButton.isVisible())) break;
await nextButton.click();
await page.waitForLoadState("networkidle");
currentPage++;
}
return allProducts;
}
async function loadAllContent(page: Page) {
const maxClicks = 50;
let clicks = 0;
const loadMoreButton = page.locator("button:has-text('Load More')");
while (clicks < maxClicks) {
if (!(await loadMoreButton.isVisible())) break;
await loadMoreButton.click();
await waitForDomSettled({ source: page });
clicks++;
}
return await extractAllProducts(page);
}
await scrollToLoadContent({
source: page,
maxScrolls: 50,
delayInMs: 100,
minHeightChange: 100
});
const products = await extractProducts(page);
Reference: docs/01-learn/recipes/ai-scraper.mdx
Use when page structure is unpredictable:
import { z } from "zod";
const ProductSchema = z.object({
name: z.string(),
price: z.number(),
rating: z.number().optional(),
inStock: z.boolean()
});
const products = await extractStructuredData({
source: page,
dataSchema: ProductSchema,
prompt: "Extract all product information from this page"
});
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
rating: float | None = None
in_stock: bool
products = await extract_structured_data(
page=page,
data_schema=Product,
prompt="Extract all product information from this page"
)
When to use: Unpredictable page structures, content that varies, fallback when selectors fail.
When NOT to use: Predictable structures (use deterministic code), high-volume scraping (adds cost).
Reference: docs/02-features/auth-sessions.mdx
Implement lightweight check API:
// check.ts - runs before every Run
export default async function check(page: Page) {
// Check for authenticated content, not absence of login
const userMenu = page.locator("[data-testid='user-menu']");
return await userMenu.isVisible({ timeout: 5000 });
}
Choose the right type:
credentials: Standard login flowsruntime: Changing credentials (OTPs, temporary tokens)recorder: Complex MFA, biometrics, human interactionSession management:
Reference: docs/01-learn/recipes/two-FA.mdx
import TOTP from "totp-generator";
async function loginWith2FA(page: Page, username: string, password: string, totpSecret: string) {
await page.fill("[name='username']", username);
await page.fill("[name='password']", password);
await page.click("button[type='submit']");
await page.locator("[name='otp']").waitFor({ state: "visible" });
const otp = TOTP.generate(totpSecret, { digits: 6 }).otp;
await page.fill("[name='otp']", otp);
await page.click("button[type='submit']");
}
Reference: docs/03-how-to/solve/structure-intuned-projects.mdx
Common patterns:
| Pattern | APIs | Use case |
|---|---|---|
| List + Details | list, details | Entity scrapers |
| Map + Scrape | map, scrape | Crawlers |
| Single action | check, download | Utilities |
Split when: Different sites, different auth, changing independently.
Combine when: Same platform, shared auth, shared code.
Reference: docs/01-learn/recipes/download-file.mdx
const download = await downloadFile({
page,
trigger: page.locator("a.download"),
timeoutInMs: 5000
});
const filename = download.suggestedFilename();
Reference: docs/01-learn/recipes/upload-files.mdx
const uploaded = await uploadFileToS3({
file: download,
s3Key: "downloads/file.pdf"
});
const signedUrl = await uploaded.getSignedUrl();
Reference: docs/01-learn/recipes/long-running-api.mdx, docs/05-references/runtime-sdk-typescript/extend-timeout.mdx
For operations exceeding the default 10-minute timeout (supports up to 6 hours):
while (hasMorePages) {
await processPage(page);
extendTimeout(); // Reset timer after each unit of work
}
Reference: docs/01-learn/recipes/extend-payload.mdx, docs/05-references/runtime-sdk-typescript/extend-payload.mdx
Chain follow-up API calls dynamically:
const products = await extractProducts(page);
for (const product of products) {
extendPayload({
api: "details",
params: { productId: product.id }
});
}
Reference: docs/02-features/observability-monitoring-logs.mdx, docs/05-references/error-codes.mdx
SDK Source: intuned-sdk/typescript-sdk/src/helpers/gotoUrl.ts
await goToUrl({
page,
url: "https://example.com",
waitForLoadState: "load",
retries: 3,
timeoutInMs: 30000
});
SDK Source: intuned-sdk/typescript-sdk/src/helpers/waitForDomSettled.ts, intuned-sdk/typescript-sdk/src/helpers/withNetworkSettledWait.ts
await waitForDomSettled({
source: page,
settleDurationMs: 500,
timeoutInMs: 30000
});
await withNetworkSettledWait(async (page) => {
await page.click("button");
}, { page, timeoutInMs: 30000 });
SDK Source: intuned-sdk/typescript-sdk/src/helpers/scrollToLoadContent.ts, intuned-sdk/typescript-sdk/src/helpers/clickUntilExhausted.ts
await scrollToLoadContent({
source: page,
maxScrolls: 50,
minHeightChange: 100
});
await clickUntilExhausted({
page,
buttonLocator: page.locator("button.load-more"),
containerLocator: page.locator(".results"),
maxClicks: 50
});
SDK Source: intuned-sdk/typescript-sdk/src/ai/extractStructuredData.ts
const data = await extractStructuredData({
source: page,
dataSchema: MySchema,
strategy: "HTML", // or "IMAGE", "MARKDOWN"
enableCache: true,
maxRetries: 3
});
SDK Source: intuned-sdk/typescript-sdk/src/helpers/downloadFile.ts, intuned-sdk/typescript-sdk/src/helpers/uploadFileToS3.ts
const download = await downloadFile({
page,
trigger: page.locator("a.download"),
timeoutInMs: 5000
});
const uploaded = await uploadFileToS3({
file: download,
s3Key: "downloads/file.pdf"
});
The following are general best practices not explicitly documented in Intuned docs but recommended for reliable automation:
Use stable, specific selectors. Avoid brittle selectors that depend on element order or styling.
Prefer semantic selectors (Playwright's role-based queries are more resilient to DOM changes):
// Preferred - semantic selectors
page.getByRole("button", { name: "Submit" })
page.getByText("Add to cart")
page.getByLabel("Email address")
page.getByPlaceholder("Enter your name")
page.getByTestId("product-card")
// Avoid - fragile selectors
page.locator("div:nth-child(3) > button")
page.locator(".btn-primary")
Selector priority order:
data-testid or similar test attributesgetByRole() with accessible namegetByLabel() for form elementsgetByText() for unique text contentgetByPlaceholder() for inputsEach evaluate() is a round trip to the browser:
// Bad - multiple round trips
const title = await page.evaluate(() => document.title);
const url = await page.evaluate(() => window.location.href);
// Good - single batched call
const data = await page.evaluate(() => ({
title: document.title,
url: window.location.href
}));
// Slow - using frameLocator
const frame = page.frameLocator("iframe#content");
await frame.locator("button").click();
// Fast - navigate directly to iframe source
const iframeSrc = await page.locator("iframe#content").getAttribute("src");
await page.goto(iframeSrc);
robots.txt and terms of service| Error | Likely cause | Solution |
|---|---|---|
| Timeout | Slow page, wrong selector | Increase timeout, fix selector |
| Element not found | DOM changed, wrong selector | Update selector, use stable attributes |
| Navigation failed | Page blocked, network issue | Add proxy, retry with backoff |
| Auth expired | Session timeout | Implement session refresh |
| Pitfall | Better approach |
|---|---|
Using waitForTimeout() with fixed delays | Wait for specific conditions |
Returning large data from evaluate() | Extract only needed fields |
| Iterating with locators for large lists | Batch extract with single evaluate() |
| Checking for login form absence | Verify authenticated content presence |
| Over-using AI agents | Use deterministic code for known patterns |
| Not closing resources | Always close pages/browsers in finally blocks |
Using pressSequentially() for input | Use fill() for instant input |
Generic selectors like .btn-primary | Use semantic selectors or test attributes |
| Hard-coded credentials in code | Use environment variables or 1Password |
Before deploying automation code, verify:
waitForTimeout() callsevaluate() callWhen reviewing automation code, use this structure:
## Automation Code Review
**Files Reviewed:** [list]
**Overall Assessment:** [brief summary]
### Issues by Priority
#### High Priority
[Reliability issues - incorrect waits, fragile selectors]
#### Medium Priority
[Performance issues - inefficient extraction, missing batching]
#### Low Priority
[Style and maintainability]
### Detailed Feedback
#### [Filename:line]
**Issue:** [description]
**Current:** `[code]`
**Suggested:** `[code]`
**Reason:** [explanation]
### What Works Well
[Positive highlights]
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience