Skip to content
Dashboard

Form Automation

Fill and submit multi-step forms across legacy web apps without APIs.

Many business-critical workflows involve filling forms in web apps that don’t have APIs — HR systems, government portals, insurance platforms, procurement tools. Lightcone automates these by driving a real browser, exactly as a human would.

fill_form.py
from tzafon import Lightcone
client = Lightcone()
with client.computer.create(kind="browser") as computer:
computer.navigate("https://httpbin.org/forms/post")
computer.wait(2)
# Fill in form fields by clicking and typing
computer.click(300, 180) # Customer name field
computer.type("Jane Doe")
computer.click(300, 230) # Telephone field
computer.type("+1-555-0123")
computer.click(300, 280) # Email field
computer.type("jane@example.com")
# Take a screenshot to verify before submitting
result = computer.screenshot()
print(f"Preview: {computer.get_screenshot_url(result)}")
# Submit
computer.click(300, 450) # Submit button
computer.wait(2)
fill_form.ts
import Lightcone from "@tzafon/lightcone";
const client = new Lightcone();
const computer = await client.computers.create({ kind: "browser" });
const id = computer.id!;
try {
await client.computers.navigate(id, { url: "https://httpbin.org/forms/post" });
await client.computers.click(id, { x: 300, y: 180 });
await client.computers.type(id, { text: "Jane Doe" });
await client.computers.click(id, { x: 300, y: 230 });
await client.computers.type(id, { text: "+1-555-0123" });
await client.computers.click(id, { x: 300, y: 280 });
await client.computers.type(id, { text: "jane@example.com" });
// Verify before submitting
const screenshot = await client.computers.screenshot(id);
console.log("Preview:", screenshot.result?.screenshot_url);
// Submit
await client.computers.click(id, { x: 300, y: 450 });
} finally {
await client.computers.delete(id);
}

Coordinates in these examples are illustrative. Take a screenshot first with computer.screenshot() to find the actual field positions on your target form.

For forms that span multiple pages (wizards):

with client.computer.create(kind="browser") as computer:
# Step 1: Personal info
computer.navigate("https://app.example.com/apply")
computer.wait(2)
computer.click(400, 200)
computer.type("Jane Doe")
computer.click(400, 260)
computer.type("jane@example.com")
computer.click(500, 400) # "Next" button
computer.wait(2)
# Step 2: Address
computer.click(400, 200)
computer.type("123 Main St")
computer.click(400, 260)
computer.type("San Francisco")
computer.click(400, 320)
computer.type("CA")
computer.click(500, 400) # "Next" button
computer.wait(2)
# Step 3: Review and submit
result = computer.screenshot()
print(f"Review: {computer.get_screenshot_url(result)}")
computer.click(500, 450) # "Submit" button
computer.wait(3)
# Confirm submission
result = computer.screenshot()
print(f"Confirmation: {computer.get_screenshot_url(result)}")
const computer = await client.computers.create({ kind: "browser" });
const id = computer.id!;
try {
// Step 1: Personal info
await client.computers.navigate(id, { url: "https://app.example.com/apply" });
await client.computers.click(id, { x: 400, y: 200 });
await client.computers.type(id, { text: "Jane Doe" });
await client.computers.click(id, { x: 400, y: 260 });
await client.computers.type(id, { text: "jane@example.com" });
await client.computers.click(id, { x: 500, y: 400 }); // Next
await new Promise((r) => setTimeout(r, 2000));
// Step 2: Address
await client.computers.click(id, { x: 400, y: 200 });
await client.computers.type(id, { text: "123 Main St" });
await client.computers.click(id, { x: 500, y: 400 }); // Next
await new Promise((r) => setTimeout(r, 2000));
// Step 3: Submit
await client.computers.click(id, { x: 500, y: 450 });
} finally {
await client.computers.delete(id);
}

For complex or dynamic forms, let the agent figure out the field layout:

for event in client.agent.tasks.start_stream(
instruction=(
"Go to https://httpbin.org/forms/post. "
"Fill in the form with: "
"Customer name: Jane Doe, "
"Telephone: +1-555-0123, "
"Email: jane@example.com, "
"Size: Medium, "
"Topping: Bacon. "
"Submit the form."
),
kind="browser",
):
print(event)
const stream = await client.agent.tasks.startStream({
instruction:
"Go to https://httpbin.org/forms/post. " +
"Fill in the form with: Customer name: Jane Doe, " +
"Telephone: +1-555-0123, Email: jane@example.com, " +
"Size: Medium, Topping: Bacon. Submit the form.",
kind: "browser",
});
for await (const event of stream) {
console.log(event);
}
  • Screenshot before submitting — always verify the form is filled correctly before clicking submit
  • Use wait() between steps — multi-step forms often have animations or redirects
  • Use the Playwright integration for forms where CSS selectors are more reliable than coordinates
  • Use persistent sessions to stay logged in across multiple form submissions
  • Use batch actions to fill multiple fields in a single request for speed