This guide creates a script task that opens a page every day, records what it sees and, when there is something to send, proposes a form submission that you approve from the dashboard.
Before you start
- Access to OpenWAF Tasks. See get access.
- A browser key of OpenWAF Browsers. The task's runs use it and count against its quota.
- An organization-wide
adminto create tasks and approve actions. Reading runs needsreadonly.
1. Write the script
A script is an ES module that exports one async function. It gets a Playwright page (and context and browser) connected to the task's browser, and task:
task.… | What it is |
|---|---|
input | The task's input JSON, set by you |
state | JSON the script saved in the last run ({} the first time) |
setState(value) | Save JSON for the next run (up to 64 KiB) |
log(message) | A line in the run's logs |
screenshot(name) | Save a screenshot of the page as an artifact of the run |
output(value) | The run's output JSON (up to 64 KiB) |
approve({summary, target, steps}) | Propose an action for a person to approve; see approvals |
export default async function ({ page, task }) {
const { url, message } = task.input;
await page.goto(url);
await page.getByRole("heading", { name: "Guestbook" }).waitFor();
await task.screenshot("before");
const already = await page.getByText(message).count();
task.log(`message on the page: ${already > 0}`);
if (already > 0) {
task.output({ posted: false, reason: "already there" });
return;
}
// The browser is read-only here: clicking "Sign" would be blocked. Propose it instead.
await task.approve({
summary: `Sign the guestbook with "${message}"`,
target: url,
steps: [
{ op: "goto", url },
{ op: "fill", selector: "#name", text: "Example Inc." },
{ op: "fill", selector: "#message", text: message },
{ op: "click", selector: "#sign" },
{ op: "wait_for", text: message },
],
});
task.setState({ proposed_at: new Date().toISOString() });
task.output({ posted: "pending approval" });
}task.approve returns the approval id (a string) once the proposal is recorded; the action runs later, after approval, in a new browser. The script never sees whether it was approved: keep the id with task.setState so the next run does not propose the same action again, and read the approval or the page in the next run.
2. Create the task
- Open Tasks in the organization menu and choose New task, kind Script.
- Name it
guestbook, pick the browser key, paste the script and set the input. - Allowed domains:
example.com. Schedule:0 9 * * *, time zoneEurope/Madrid. - Save.
curl -sS -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/tasks" \
-H "Authorization: Bearer $OPENWAF_TOKEN" -H "Content-Type: application/json" \
-H "X-Change-Reason: Daily guestbook entry" \
--data-binary @- <<EOF
{"name": "guestbook", "kind": "script",
"schedule": {"cron": "0 9 * * *", "timezone": "Europe/Madrid"},
"browser": {"key_id": "$KEY_ID", "profile": "", "recording": false},
"allow": ["example.com"],
"script": $(jq -Rs . < guestbook.mjs),
"input": {"url": "https://example.com/guestbook", "message": "Hello from Example Inc."}}
EOFThe 201 answer carries the task with its id and next_run_at. Add ?dry_run=true to only validate it.
Put the same JSON body in task.json, then:
openwaf-cloud ctl tasks create task.json --org acme --reason "Daily guestbook entry"3. Run it now
Choose Run now on the task, or call POST /v1/orgs/{org}/tasks/{task}/run, or openwaf-cloud ctl tasks run guestbook --org acme --reason "First run". The answer is the run (202). Follow its logs with GET /v1/orgs/{org}/task-runs/{run}/logs?after=<seq>. While it runs, live_url lets you watch the browser.
When the script proposes the action, the run goes to awaiting_approval and the proposal appears in Tasks → Approvals.
4. Approve it
Open the approval. Check the summary, the target, every step and the screenshot, then choose Approve. From the API you send back the hash you saw:
curl -sS -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/task-approvals/$APPROVAL/approve" \
-H "Authorization: Bearer $OPENWAF_TOKEN" -H "Content-Type: application/json" \
-d '{"payload_sha256": "9c1e…", "reason": "Text checked"}'The runner replays the steps in a new browser, takes a final screenshot and stores the result in the approval. The run ends succeeded or failed.