playwright-plugin-web-from-json
v1.0.22
Published
Generate tests Web from JSONs
Readme
✨ Playwright Web-from-JSON
npm init --y
npm init playwright
npm i playwright-plugin-web-from-jsonCreated file/folder structure
.
├─ hooks/
│ └─ before-json.json # A JSON “before” chain you can reuse from other JSONs
├─ Fixtures/
│ ├─ playwright-plugin-web-from-json.json # Full Scenario Lab suite (many actions/assertions)
│ ├─ before-plugin.json # Example that reuses `../hooks/before-json.json`
│ ├─ plugin-example-voe-latam.json # Example flow on LATAM site (Brazil)
│ └─ plugin-example-auto.json # Example flow on your demo app (GitHub Pages)
├─ html/
│ └─ index.html # “Scenario Lab” static page used by tests
├─ help/
│ └─ plugin-func.ts # Implements class `RunPluginFunctions` for `"run"` actions
└─ tests/ or e2e/
└─ json-plugin.spec.ts # Playwright spec that loads JSON from `Fixtures/`If neither
tests/nore2e/exists,tests/will be created and used by default.
File-by-file details
hooks/before-json.json
Reusable pre-test chain that other JSON files can reference via "before": "../hooks/before-json.json".
- Example content opens the LATAM site and performs a minimal wait.
- Good place to add login flows, cookies, feature flags, etc.
{
"describe": {
"text": "Before",
"url": "https://www.latamairlines.com/br/pt",
"case-key": {
"title": "Buy ticket",
"actions": [{ "wait": 3000 }]
}
}
}Fixtures/playwright-plugin-web-from-json.json
Main showcase suite targeting html/index.html (the Scenario Lab). It demonstrates most of the plugin’s capabilities:
- Navigation & URL assertions (
expectUrlwithcontains) - Click by selector or exact text
hover+expectVisibletooltiptype/typeSlow/press(Enter) with faker support- Form controls:
check, radio,select,upload expectTextwith and withoutloc- Multiple matches with
nth/first/last - Scoped searches with
within - Iframe interactions with
frame - Network synchronization with
waitRequest - Modal visibility +
screenshottarget - A
forEachplayground that iterates over generated cards
Target URL for this suite:
{ "url": "http://127.0.0.1:5500/html/index.html" }Tip: use Live Server in VS Code to serve
/html/index.htmlathttp://127.0.0.1:5500/html/index.html.
Fixtures/before-plugin.json
Automation example that shows how to chain a reusable “before” file:
{
"describe": {
"text": "Using Test before Funcion",
"before": "../hooks/before-json.json",
"wait before each test": {
"text": "wait before each test",
"actions": [{ "wait": 3000 }]
}
}
}Use this pattern to share setup logic across multiple JSON suites (auth bootstrap, cookies, etc.).
Fixtures/plugin-example-voe-latam.json
Real-world example that opens LATAM Airlines and performs a small flow using actions like root, parent, click, type, expectVisible, and simple date clicks. It demonstrates:
- Mixed scoping (
root+parent) to shorten selectors - Text-based interactions (click by visible label)
- Basic calendar interaction (example dates)
- Passengers selector interaction
waitto stabilize steps
You can tailor the selectors/dates according to the site’s current layout and locales.
Fixtures/plugin-example-auto.json
Example using your demo web app hosted on GitHub Pages:
- Logs in with
faker-generated entries - Performs a register flow, including
selectandparentscoping - Demonstrates
frame+root+runcombos andtypeSlow - Shows a Tasks flow using
runreturn values to fill fields
This file is great to see run in action paired with dynamic fields:
{ "run": "randomUser" }
{ "type": "{resultFunc.username}" }html/index.html (Scenario Lab)
A static page purposely built to exercise plugin actions/assertions in a deterministic way:
- Hover tooltips
- Buttons that show toasts/labels
- Inputs for typing/press
- Radio/checkbox/select/upload
- Repeated rows for
nth/first/last - A small iframe with a button and toast
- Network buttons calling JSONPlaceholder
- A modal dialog
- A forEach playground that generates a grid of cards (with inner buttons)
Serve locally with VS Code Live Server:
- Install the extension “Live Server”
- Right-click
html/index.html→ Open with Live Server - Confirm the URL used in the JSON:
http://127.0.0.1:5500/html/index.html
help/plugin-func.ts (RunPluginFunctions) NOT CHANGE NAME
Implements export class RunPluginFunctions that your JSON can call with "run": "<methodName>". Methods may be sync or async and can return strings, numbers, or objects. Returned values are exposed under {resultFunc} (or resultFunc.* for object fields).
Provided examples:
export class RunPluginFunctions {
// not change name class
hello() {
return { greeting: "hello", email: "[email protected]" };
}
async randomUser() {
return { username: "user_" + Math.random().toString(36).slice(2, 7) };
}
async delayedCode() {
await new Promise((r) => setTimeout(r, 1000));
return "CODE-" + Math.floor(Math.random() * 999);
}
randomCode() {
return Math.floor(Math.random() * 10000);
} // number
userEmail() {
return "user_" + Date.now() + "@example.com";
} // string
}Usage in JSON:
{ "run": "randomUser" }
{ "type": "{resultFunc.username}", "loc": "#taskDescription" }
{ "run": "hello" }
{ "typeSlow": "{resultFunc.greeting}", "loc": "#address" }The loader tries multiple export styles: named export, default class, or default object with
RunPluginFunctions. Keep the class name and export consistent.
tests/json-plugin.spec.ts (or e2e/json-plugin.spec.ts)
The Playwright spec that generates tests from all JSON files placed in Fixtures/:
import { test } from "@playwright/test";
import { generateTestsFromJson } from "playwright-plugin-web-from-json";
import path from "path";
generateTestsFromJson(
{
dir: path.resolve(process.cwd(), "Fixtures"),
// baseURLOverride: "http://127.0.0.1:5500/html/index.html",
// functionsPath: path.resolve(process.cwd(), "help/plugin-func.ts"),
allowNoopWhenEmpty: true,
},
test
);dir: folder to scan for JSON suitesbaseURLOverride: point all cases to a given base URL (handy for Scenario Lab)functionsPath: direct path to yourRunPluginFunctionsallowNoopWhenEmpty: avoid failure if a JSON ends up empty (optional convenience)
config/before-config.ts
This file is automatically created by the setup script. It serves as a single place to centralize any logic you want to run before each test (e.g., authentication, cookies, feature flags, viewport, locale, storageState, etc.). By default, nothing runs — it simply exports an extended test so you can enable fixtures/hooks later.
What is generated
// config/before-config.ts
import { test as base, expect } from "@playwright/test";
// (Optional) Define fixture types here if you plan to add any later.
type Fixtures = {};
// Extend Playwright's test. For now, no custom fixtures or hooks are active.
// This file is a placeholder so you can easily enable per-test context/page or
// any "before each" logic later without changing your runner signature.
export const test = base.extend<Fixtures>({
// Example (disabled): provide a custom BrowserContext per test
// context: async ({ browser }, use) => {
// const context = await browser.newContext({ /* options */ });
// await use(context);
// await context.close();
// },
// Example (disabled): provide a Page per test and run pre-test actions
// page: async ({ context }, use) => {
// const page = await context.newPage();
// // Place any per-test setup here (cookies, flags, login, etc.)
// await use(page);
// await page.close();
// },
});
// Global hooks — currently no-ops. Keep them to quickly add logic later if needed.
test.beforeEach(
async (
{
/* page, context */
}
) => {
// Reserved for actions to run before each test.
}
);
test.afterEach(
async (
{
/* page, context */
}
) => {
// Reserved for actions to run after each test.
}
);
export { expect };Use recomended config in playwright.config.ts
🚀 Running the Test HTML
To run the test HTML:
- Install the Live Server extension in VS Code.
- Open the file:
html/index.html - Right-click anywhere in the file.
- Select "Open with Live Server".
💡 This will automatically open the page in your default browser
🖼️ Example: Opening with Live Server

If you don’t see this option, make sure the Live Server extension is properly installed and enabled in VS Code.
Using url
{ "url": "https..." } // absolute url
{ "url": "/products" } // baseUrl (playwright.config.ts) + /products
{ } // to continue automation on the current page, do not use the key url
🛠️ Actions Reference → Playwright
| Action | JSON (2 examples) | Playwright reference (2 examples) |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | { "loc": "#user", "type": "John" }{ "loc": "input[name='email']", "type": "[email protected]" } | await page.locator('#user').fill('John')await page.locator('input[name="email"]').fill('[email protected]') |
| typeSlow | { "loc": "#msg", "typeSlow": "Hello" }{ "loc": ".editor textarea", "typeSlow": "Long text…" } | await loc.fill(''); await loc.pressSequentially('Hello', { delay: 300 })await page.locator('.editor textarea').pressSequentially('Long text…', { delay: 300 }) |
| click | { "click": "button > Save" }{ "click": "a:has-text('Docs')" } | await page.locator('button', { hasText: 'Save' }).click()await page.locator("a:has-text('Docs')").click() |
| click: "{type}" | { "loc": "#q", "type": "Playwright" }, { "click": "{type}" }{ "type": "Neo", "loc": "input[name='who']" }, { "click": "{type}" } | await page.locator('#q').fill('Playwright'); await page.getByText('Playwright', { exact: true }).click()await page.locator("input[name='who']").fill('Neo'); await page.getByText('Neo', { exact: true }).click() |
| click: "<prefix> {type}" | { "type": "Item 1", "loc": "#search" }, { "click": "ul.results {type}" }{ "type": "Settings", "loc": "#menu-filter" }, { "click": "nav {type}" } | await page.locator('#search').fill('Item 1'); await page.locator('ul.results *:has-text("Item 1")').click()await page.locator('#menu-filter').fill('Settings'); await page.locator('nav *:has-text("Settings")').click() |
| hover | { "hover": ".menu" }{ "hover": "button:has-text('Preview')" } | await page.locator('.menu').hover()await page.locator("button:has-text('Preview')").hover() |
| press | { "press": "Enter", "loc": "#q" }{ "press": "Escape" } | await page.locator('#q').press('Enter')await page.keyboard.press('Escape') |
| check / uncheck | { "check": "#agree" }{ "uncheck": ".todo-list li:nth-child(1) .toggle" } | await page.locator('#agree').check()await page.locator('.todo-list li:nth-child(1) .toggle').uncheck() |
| select | { "select": { "label": "Brazil" }, "loc": "#country" }{ "select": { "value": "us" }, "loc": "select#country" } | await page.locator('#country').selectOption({ label: 'Brazil' })await page.locator('select#country').selectOption({ value: 'us' }) |
| upload | { "upload": { "loc": "input[type=file]", "files": ["fixtures/a.png"] } }{ "upload": { "loc": "input[type=file]", "files": ["fixtures/a.png","fixtures/b.png"] } } | await page.locator('input[type=file]').setInputFiles('fixtures/a.png')await page.locator('input[type=file]').setInputFiles(['fixtures/a.png','fixtures/b.png']) |
| exist | { "exist": "#close-popup", "click": "#close-popup" }{ "exist": "Promotion", "click": "Fechar" } | Soft-check element then run remaining keys if foundSoft-check text then click close |
| getText | { "getText": "h1" }{ "getText": ".card .title" } | const t = await page.locator('h1').textContent()const t = await page.locator('.card .title').textContent() |
| expectText | { "expectText": { "contains": "Welcome" } }{ "loc": ".toast", "expectText": { "equals": "Saved!" } } | await expect(page.locator('body')).toContainText('Welcome')await expect(page.locator('.toast')).toHaveText('Saved!') |
| expectVisible | { "expectVisible": "#toast" }{ "loc": ".modal", "expectVisible": { "timeout": 2000 } } | await expect(page.locator('#toast')).toBeVisible()await expect(page.locator('.modal')).toBeVisible({ timeout: 2000 }) |
| expectUrl | { "expectUrl": { "contains": "/home" } }{ "expectUrl": { "equals": "https://app.test/dashboard" } } | await expect(page).toHaveURL(/\/home/)await expect(page).toHaveURL('https://app.test/dashboard') |
| waitRequest | { "waitRequest": { "url": "/api/save", "status": 200 } }{ "waitRequest": { "url": "**/users", "method": "POST" } } | await handleWaitRequest(page, { url:'/api/save', status:200 })await handleWaitRequest(page, { url:'**/users', method:'POST' }) |
| wait | { "wait": 500 }{ "wait": 1500 } | await page.waitForTimeout(500)await page.waitForTimeout(1500) |
| screenshot | { "screenshot": { "path": "shots/home.png", "fullPage": true } }{ "loc": ".card", "screenshot": { "path": "shots/card.png" } } | await page.screenshot({ path:'shots/home.png', fullPage:true })await page.locator('.card').screenshot({ path:'shots/card.png' }) |
| forEach | { "forEach": { "items": ".product-card", "actions": [ { "click": "button:has-text('Details')" } ] } }{ "forEach": { "items": "article.post", "actions": [ { "getText": "h2.title" } ] } } | Itera itens e executa ações aninhadas |
| scrollTo | "bottom"{ "to": "h2:has-text('Installation')" } | await page.evaluate(() => window.scrollTo({ top: document.body.scrollHeight }))await page.locator("h2:has-text('Installation')").scrollIntoViewIfNeeded() |
| expectValue | { "expectValue": { "loc": "input[name='email']", "equals": "[email protected]" } }{ "expectValue": { "loc": "#q", "contains": "runner" } } | await expect(page.locator("input[name='email']")).toHaveValue("[email protected]")expect(await page.locator('#q').inputValue()).toContain('runner') |
| route | { "route": { "url": "**/api/users", "mock": { "status": 200, "json": [{ "id":1,"name":"Neo"}] } } }{ "route": { "unroute": "**/api/users" } } | await page.route("**/api/users", r => r.fulfill({ status:200, headers:{'content-type':'application/json'}, body: JSON.stringify([{id:1,name:'Neo'}]) }))await page.unroute("**/api/users") |
| run | { "run": "buildUser" }{ "run": "nowISO" } | Executa RunPluginFunctions.buildUser() / nowISO() |
✅ Complete context example (realistic)
{
"describe": {
"text": "Context + Indexing",
"url": "https:....",
"users": {
"title": "Open the 3rd user's details from nested nav",
"context": {
"iframe": ["iframe#shell", "iframe#app"], // page.frameLocator(...).frameLocator(...)
"root": "nav#sidebar", // scope.locator("nav#sidebar")
"parent": "Management", // scope.getByText("Management", { exact: true })
"index": 2, // climb 2 times: locator("..").locator("..")
"within": "ul.menu", // restrict to "ul.menu"
"nth": 2 // use 3rd match by default inside this context
},
"actions": [
{ "click": "li > Users" }, // -> scoped + nth(2)
{ "expectVisible": "h1 > Users" },
{ "click": "tr:nth-child(3) a > Details" }, // specific selector beats nth default
{ "expectUrl": { "contains": "/users/" } }
]
}
}
}Playwright mapping (conceptual):
scope = page.frameLocator('#shell').frameLocator('#app').locator('nav#sidebar');parent = scope.getByText('Management', { exact: true });climbed = parent.locator('..').locator('..');
scope = climbed.locator('ul.menu');
locator = scope.locator('li', { hasText: 'Users' }).nth(2);
Note: Setting
nth/first/lastat action-level overrides the case-levelcontextindexing for that action.
📌 Case metadata
title: string — test nameurl: string — if relative, resolved against PlaywrightbaseURL(oropts.baseURLOverride)actions: array of action objects (see below)
At describe level:
text: string —describetitleurl: default URL for casesbefore:string | string[]— one or more JSON files executed before each case of this file (child JSONs' ownbeforeis ignored by design)
🧰 Actions (full list)
For each action below you’ll see: JSON shape, Playwright mapping, and 2 complete examples.
1) click
- Shape:
{ "click": string } - Map:
locator(target).click()
{ "click": "button#submit" }{ "click": "a > Continue" }2) type
- Shape:
{ "loc"?: string, "click"?: string (selector), "type": string } - Map:
locator(target).fill(text)
{ "loc": "#email", "type": "[email protected]" }{ "click": "#search", "type": "Playwright" }3) typeSlow
- Map:
locator(target).pressSequentially(text, { delay: 300 })
{ "loc": "#query", "typeSlow": "slow typing..." }{ "click": "#search", "typeSlow": "faker.internet.username()" }4) hover
- Map:
locator(target).hover()
{ "hover": ".menu .item.settings" }{ "within": "nav#top", "hover": "a > Admin" }5) press
- Shape:
{ "press": "Key", "loc"?: string } - Map:
page.keyboard.press(key)orlocator(target).press(key)
{ "press": "Escape" }{ "loc": "#search", "press": "Enter" }6) check / 7) uncheck
- Shape:
"string" | { "loc": string } | true(if omitted, falls back toloc/clickon action/case) - Map:
locator(target).check()/.uncheck()
{ "check": { "loc": "#terms" } }
{ "check": "#terms" }
{ "uncheck": "#newsletter" }8) select
- Shape:
{ "select": { "value"|"label"|"index": string|string[]|number }, "loc"?: string, "click"?: string } - Map:
locator(target).selectOption(...)
{ "select": { "label": "Pernambuco" }, "loc": "#state" }{ "loc": "#multi", "select": { "value": ["A", "C"] } }9) upload
- Shape:
{ "upload": string | string[], "loc"?: string, "click"?: string } - Map:
locator(target).setInputFiles(files)
{ "loc": "#file", "upload": "fixtures/id.pdf" }{ "loc": "#docs", "upload": ["a.pdf", "b.pdf"] }10) expectText
- Shape:
{ "expectText": { "equals"?: any, "contains"?: any, "timeout"?: number }, "loc"?: string, "click"?: string } - Map:
expect(locator).toHaveText(v)/expect(locator).toContainText(v); if no target, assert on page text
{ "loc": "h1", "expectText": { "equals": "Dashboard" } }{ "expectText": { "contains": "Welcome back" } }11) expectVisible
- Shape:
"string"or{ "expectVisible": { "timeout"?: number }, "loc"?: string } - Map:
expect(locator).toBeVisible({ timeout })
{ "expectVisible": "#profile" }{ "loc": "button#pay", "expectVisible": { "timeout": 5000 } }12) expectValue
- Shape:
{ "expectValue": { "loc": string, "equals"?: any, "contains"?: any, "timeout"?: number } } - Map:
expect(locator).toHaveValue(v)or read value andtoContain
{ "expectValue": { "loc": "#email", "equals": "[email protected]" } }{ "expectValue": { "loc": "#search", "contains": "jam" } }13) expectUrl
- Shape:
{ "expectUrl": { "equals"?: string, "contains"?: string, "timeout"?: number } } - Map:
expect(page).toHaveURL(url)/expect(page).toHaveURL(new RegExp(escapeRegex(contains)))
{ "expectUrl": { "equals": "https://mysite.com/checkout" } }{ "expectUrl": { "contains": "/checkout" } }14) exist (gate)
- Shape:
{ "exist": string } - Effect: if target does not exist, the current action is skipped (no throw)
- Map:
await locator.count() > 0
{ "exist": "#toast-success" }{ "exist": "Operation completed" }15) forEach
- Shape:
{ "forEach": { "items": string, "actions": Action[] } } - Map: iterate
locator(items),count(), then for eachnth(i)run sub-actions withitemas base scope.
Complete Example A — iterate rows and open modal
{
"describe": {
"text": "forEach demo",
"url": "/produts",
"rows": {
"title": "Open each row details and close",
"actions": [
{
"forEach": {
"items": "table tbody tr",
"actions": [
{ "getText": "td:nth-child(2)" },
{ "click": "button > Open" },
{ "expectVisible": ".modal" },
{ "screenshot": { "path": "shots/modal-row.png" } },
{ "press": "Escape" }
]
}
}
]
}
}
}Complete Example B — with context inside each item
{
"describe": {
"text": "forEach + context",
"url": "/products",
"cards": {
"title": "Click inner CTA per card",
"actions": [
{
"forEach": {
"items": ".card",
"actions": [
{ "within": ".footer" },
{ "click": "button > CTA" },
{ "expectText": { "contains": "Done" } }
]
}
}
]
}
}
}Tip (recommended upgrade): Expose loop index as
__indexinvarsso you can use"shots/card-{__index}.png".
16) getText
- Shape:
{ "getText": string } - Effect: logs and stores the text into an internal memo (
lastGetText); (recommended upgrade: copy intovars.lastGetTextto enable{lastGetText}tokens)
{ "getText": "h2 > Order Summary" }{ "getText": ".total" }17) route
- Subkeys:
block,unroute,url + mock - Map:
block:page.route(pattern, r => r.abort())unroute:page.unroute(pattern)url+mock:page.route(url, r => r.fulfill({ status, headers, body|json }))
{ "route": { "block": ["**/analytics/**", "**/maps/**"] } }{
"route": {
"url": "**/api/profile",
"mock": { "status": 200, "json": { "name": "Jam", "role": "QA" } }
}
}18) waitResponse
- Shape:
{ "waitResponse": { "url": string(glob), "status"?: number, "bodyContains"?: string, "timeout"?: number } } - Map:
page.waitForResponse(fn, { timeout })with a glob→RegExp filter; then optional body contains check
{ "waitResponse": { "url": "**/api/payments/**", "status": 200 } }{
"waitResponse": {
"url": "**/api/orders/**",
"status": 201,
"bodyContains": "\"state\":\"created\""
}
}19) waitRequest
- Shape:
{ "waitRequest": { ... } }(delegated tohandleWaitRequest(page, config)in your code) - Map: implementation-specific — typically
page.waitForRequest(...)
{ "waitRequest": { "url": "**/api/search**" } }{ "waitRequest": { "method": "POST", "url": "**/api/login" } }20) wait
- Shape:
{ "wait": number(ms) } - Map:
page.waitForTimeout(ms)
{ "wait": 800 }{ "wait": 2500 }21) scrollTo
- Shape:
"top" | "bottom"or{ "x"?: number, "y"?: number, "to"?: string, "behavior"?: "auto"|"smooth" } - Map:
page.evaluate(window.scrollTo(...))orlocator(target).scrollIntoViewIfNeeded()
{ "scrollTo": "bottom" }{ "scrollTo": { "to": "h2 > Details" } }22) screenshot
- Shape:
{ "screenshot": { "path": string, "fullPage"?: boolean }, "loc"?: string } - Map:
page.screenshot()orlocator(target).screenshot()
{ "screenshot": { "path": "shots/home.png", "fullPage": true } }{ "loc": ".invoice", "screenshot": { "path": "shots/invoice.png" } }23) run
- Shape:
{ "run": "methodName" }— calls a method onRunPluginFunctions - Effect: return value stored in
vars.resultFunc(awaited if Promise)
{ "run": "genCPF", "as": "cpf" }
// {type: "{cpf}"}{ "run": "nowISO" }Create new functions in
help/plugin-func.ts:
{
"describe": {
"text": "Case-level run",
"url": "http://127.0.0.1:5500/html/index.html",
"fill-name-with-userEmail": {
"title": "Case run → type",
"run": "userEmail",
"actions": [
{ "click": "Typing & Keys" },
{ "loc": "#name", "type": "{resultFunc}" }
]
},
"fill-using-alias": {
"title": "Case run with alias",
"run": "hello",
"as": "user",
"actions": [
{ "click": "Typing & Keys" },
{ "loc": "#name", "typeSlow": "{user.greeting}" },
{ "loc": "#email", "type": "{resultFunc.email}" }
]
}
}
}export class RunPluginFunctions {
hello() {
return { greeting: "hello", email: "[email protected]" };
}
userEmail() {
return "user_" + Date.now() + "@example.com";
}
}{
"describe": {
"text": "Action-level run (inline)",
"url": "http://127.0.0.1:5500/html/index.html",
"inline-run-and-type": {
"title": "run + type no mesmo action",
"actions": [
{ "click": "Typing & Keys" },
{ "run": "userEmail", "as": "user", "loc": "#email", "type": "{user}" }
]
}
}
}{
"describe": {
"text": "Async run",
"url": "http://127.0.0.1:5500/html/index.html",
"async-then-use": {
"title": "await run, depois usar campos",
"actions": [
{ "run": "fetchProfile", "as": "profile" },
{ "click": "Typing & Keys" },
{ "loc": "#name", "type": "{profile.name}" },
{ "loc": "#email", "type": "{profile.email}" }
]
}
}
}export class RunPluginFunctions {
async fetchProfile() {
// simulação de IO
await new Promise((r) => setTimeout(r, 300));
return { name: "Ada Lovelace", email: "[email protected]" };
}
}{
"describe": {
"text": "Simple return types",
"url": "http://127.0.0.1:5500/html/index.html",
"number-into-input": {
"title": "Número → string no input",
"actions": [
{ "run": "randomCode" },
{ "click": "Typing & Keys" },
{ "loc": "#name", "type": "{resultFunc}" }
]
},
"boolean-into-branch": {
"title": "Booleano e expectText",
"actions": [
{ "run": "featureFlag", "as": "flag" },
{ "click": "Clicks & Visibility" },
{ "click": "Make toast visible" },
{ "expectText": { "contains": "OK" }, "loc": "#selector-result" }
]
}
}
}export class RunPluginFunctions {
randomCode() {
return Math.floor(Math.random() * 10000);
} // number
featureFlag() {
return true;
} // boolean (vira "true"/"false" quando interpolado)
}{
"describe": {
"text": "Object shape",
"url": "http://127.0.0.1:5500/html/index.html",
"deep-object": {
"title": "Usar caminhos do objeto",
"actions": [
{ "run": "buildUser", "as": "user" },
{ "click": "Typing & Keys" },
{ "loc": "#name", "type": "{user.profile.fullName}" },
{ "loc": "#email", "type": "{user.contacts.primary}" }
]
}
}
}export class RunPluginFunctions {
buildUser() {
return {
profile: { fullName: "Grace Hopper" },
contacts: {
primary: "[email protected]",
backup: "[email protected]",
},
};
}
}example in JSON Fixtures/plugin-examplo-auto.json
🧠 Token interpolation (recap)
You can place {token} in most string fields, including nested ones (expect*, select, screenshot.path, etc.). Path tokens like {user.name} are supported; avoid hyphenated keys inside paths (use user.fullName not user.full-name).
Specials available during a test:
{resultFunc}— last return of a"run"action.- (Recommended upgrade)
{lastTypedText},{lastGetText},{__index}— if you expose them invars.
Examples:
{ "run": "genCPF" },
{ "loc": "#cpf", "type": "{resultFunc}" }{ "run": "genCPF", "as":"user_cpf" },
{ "loc": "#cpf", "type": "{user_cpf}" }{ "screenshot": { "path": "shots/card-{__index}.png" } }🧪 Complete example: forEach + nth + first/last override
{
"describe": {
"text": "Catalog",
"url": "/products",
"open-cards": {
"title": "Open specific cards with indexing",
"context": {
"root": ".catalog",
"within": ".cards",
"nth": 0
},
"actions": [
// Default index is nth(0) due to context
{ "click": ".card > h3" },
// Override index at action-level: last card CTA
{ "last": true, "click": ".card button > Details" },
// Explicit nth at action-level: 5th card
{ "nth": 4, "click": ".card > h3" },
// first at action-level
{ "first": true, "click": ".card > h3" }
]
},
"iterate-cards": {
"title": "Iterate cards and open+close modal",
"actions": [
{
"forEach": {
"items": ".card",
"actions": [
{ "within": ".footer" },
{ "click": "button > Open" },
{ "expectVisible": ".modal" },
{ "press": "Escape" }
]
}
}
]
}
}
}Using faker in type or typeSlow
Using faker with type / typeSlow
The plugin’s type and typeSlow fields accept any Faker v10 API call exactly as documented: https://fakerjs.dev
Supported forms (real examples)
No args
faker.internet.email()faker.person.fullName()faker.location.streetAddress()
Single number arg
faker.string.alphanumeric(12)faker.number.int(9999)faker.number.float(2)
Options object
faker.number.int({ min: 100, max: 999 })faker.finance.amount({ min: 10, max: 5000, dec: 2 })faker.date.past({ years: 1 })faker.date.soon({ days: 3 })
Array (or multiple) args
faker.string.fromCharacters(['A','B','C'], 8)faker.helpers.arrayElement(['BR','US','AR'])faker.phone.number(['+55 11 ####-####', '+55 21 ####-####'])
Mixed / specific formatting
faker.internet.userName('john_doe')faker.date.between({ from: '2020-01-01T00:00:00.000Z', to: '2030-01-01T00:00:00.000Z' })faker.phone.number('(+55 ##) 9####-####')faker.commerce.price({ min: 9.9, max: 199.9 })
For the complete list of modules, methods, and arguments, see https://fakerjs.dev.

