Skip to main content

Add a Custom DSL Action

The base DSL grammar (goto, click, extractText, and so on — see the DSL Grammar reference) is deliberately fixed and use-case agnostic. A Challenger extension can add to it without touching packages/dsl, by registering an action.

r.registerAction({
action: 'solveCaptcha',
schema: z.object({
action: z.literal('solveCaptcha'),
selector: z.string().optional(),
}),
execute: async (ctx) => {
const solved = await this.handle(ctx);
return solved ? { output: { solved: true } } : { preconditionFailed: true };
},
});

Once registered, solveCaptcha is valid in any YAML flow:

steps:
- action: goto
value: "https://example.com/login"
- action: solveCaptcha
selector: "#captcha-widget"

How it resolves

The action's schema is unioned into the compiled step schema, so the DSL compiler accepts it like a base action. At runtime, step-executor.ts looks up any action not in its built-in handler table through session.resolveAction(name) and calls your execute(ctx), mapping the returned ChallengerActionResult onto the step's result.

Constraints

  • capabilities must include dsl-action, or the registration is dropped.
  • The action name must not collide with a base DSL action name or another extension's — the registry rejects both at registration time, not silently.
  • execute receives a ChallengerStepContext, the same handler context shape as beforeStep/afterStep, so ctx.state, ctx.helpers, and asPage(ctx) all work the same way here.

Next steps