@lowdefy/docs-content 0.0.0-experimental-20260710073520

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +20 -0
  3. package/content/actions/callapi.md +66 -0
  4. package/content/actions/callmethod.md +41 -0
  5. package/content/actions/copytoclipboard.md +32 -0
  6. package/content/actions/displaymessage.md +53 -0
  7. package/content/actions/downloadcsv.md +89 -0
  8. package/content/actions/fetch.md +50 -0
  9. package/content/actions/geolocationcurrentposition.md +56 -0
  10. package/content/actions/link.md +104 -0
  11. package/content/actions/login.md +85 -0
  12. package/content/actions/logout.md +56 -0
  13. package/content/actions/pdfmake.md +304 -0
  14. package/content/actions/request.md +67 -0
  15. package/content/actions/reset.md +27 -0
  16. package/content/actions/resetvalidation.md +85 -0
  17. package/content/actions/scrollto.md +85 -0
  18. package/content/actions/setdarkmode.md +62 -0
  19. package/content/actions/setfocus.md +33 -0
  20. package/content/actions/setglobal.md +63 -0
  21. package/content/actions/setlocale.md +69 -0
  22. package/content/actions/setstate.md +70 -0
  23. package/content/actions/throw.md +72 -0
  24. package/content/actions/validate.md +87 -0
  25. package/content/actions/wait.md +24 -0
  26. package/content/agents/agent-endpoint-tools.md +190 -0
  27. package/content/agents/agent-mcp-tools.md +124 -0
  28. package/content/agents/agent-multi-agent.md +136 -0
  29. package/content/agents/agent-properties.md +174 -0
  30. package/content/agents/agent-server-hooks.md +336 -0
  31. package/content/agents/agentchat.md +515 -0
  32. package/content/agents/agentconversations.md +148 -0
  33. package/content/concepts/ai-agent-docs.md +190 -0
  34. package/content/concepts/cli.md +213 -0
  35. package/content/concepts/connections-and-requests.md +7 -0
  36. package/content/concepts/custom-html.md +3 -0
  37. package/content/concepts/custom-styling.md +161 -0
  38. package/content/concepts/events-and-actions.md +271 -0
  39. package/content/concepts/hosting-files.md +18 -0
  40. package/content/concepts/i18n.md +320 -0
  41. package/content/concepts/keyboard-shortcuts.md +218 -0
  42. package/content/concepts/layout-overview.md +478 -0
  43. package/content/concepts/lists.md +144 -0
  44. package/content/concepts/logger.md +43 -0
  45. package/content/concepts/lowdefy-schema.md +365 -0
  46. package/content/concepts/menus.md +144 -0
  47. package/content/concepts/module-authoring.md +564 -0
  48. package/content/concepts/modules.md +338 -0
  49. package/content/concepts/operators.md +45 -0
  50. package/content/concepts/overview.md +19 -0
  51. package/content/concepts/references-and-templates.md +207 -0
  52. package/content/concepts/secrets.md +24 -0
  53. package/content/concepts/selectors.md +143 -0
  54. package/content/connections/amazonredshift.md +176 -0
  55. package/content/connections/anthropic.md +232 -0
  56. package/content/connections/awss3.md +313 -0
  57. package/content/connections/azureblobstorage.md +133 -0
  58. package/content/connections/elasticsearch.md +371 -0
  59. package/content/connections/google.md +228 -0
  60. package/content/connections/googlecloudstorage.md +139 -0
  61. package/content/connections/googlesheet.md +313 -0
  62. package/content/connections/knex.md +206 -0
  63. package/content/connections/mariadb.md +177 -0
  64. package/content/connections/mcp.md +134 -0
  65. package/content/connections/mongodb.md +739 -0
  66. package/content/connections/mssql.md +191 -0
  67. package/content/connections/mysql.md +192 -0
  68. package/content/connections/openai-connection.md +228 -0
  69. package/content/connections/postgresql.md +200 -0
  70. package/content/connections/redis.md +116 -0
  71. package/content/connections/sendgridmail.md +94 -0
  72. package/content/connections/smtp.md +149 -0
  73. package/content/connections/sqlite.md +187 -0
  74. package/content/connections/stripe.md +115 -0
  75. package/content/container-blocks/affix.md +413 -0
  76. package/content/container-blocks/alert.md +937 -0
  77. package/content/container-blocks/badge.md +1222 -0
  78. package/content/container-blocks/box.md +1453 -0
  79. package/content/container-blocks/card.md +1200 -0
  80. package/content/container-blocks/carousel.md +2247 -0
  81. package/content/container-blocks/collapse.md +1335 -0
  82. package/content/container-blocks/configprovider.md +1070 -0
  83. package/content/container-blocks/confirmmodal.md +1159 -0
  84. package/content/container-blocks/content.md +597 -0
  85. package/content/container-blocks/descriptions.md +870 -0
  86. package/content/container-blocks/drawer.md +1379 -0
  87. package/content/container-blocks/dropdownmenu.md +643 -0
  88. package/content/container-blocks/flex.md +1510 -0
  89. package/content/container-blocks/footer.md +380 -0
  90. package/content/container-blocks/googlemaps.md +546 -0
  91. package/content/container-blocks/googlemapsheatmap.md +607 -0
  92. package/content/container-blocks/googlemapsscript.md +61 -0
  93. package/content/container-blocks/header.md +302 -0
  94. package/content/container-blocks/label.md +739 -0
  95. package/content/container-blocks/layout.md +604 -0
  96. package/content/container-blocks/masonry.md +1090 -0
  97. package/content/container-blocks/mobilemenu.md +1244 -0
  98. package/content/container-blocks/modal.md +1180 -0
  99. package/content/container-blocks/pageheadermenu.md +614 -0
  100. package/content/container-blocks/pagesidebarlayout.md +438 -0
  101. package/content/container-blocks/pagesidermenu.md +651 -0
  102. package/content/container-blocks/popover.md +1421 -0
  103. package/content/container-blocks/result.md +972 -0
  104. package/content/container-blocks/sider.md +1064 -0
  105. package/content/container-blocks/span.md +747 -0
  106. package/content/container-blocks/spinner.md +267 -0
  107. package/content/container-blocks/splitter.md +1735 -0
  108. package/content/container-blocks/tabs.md +1718 -0
  109. package/content/container-blocks/tooltip.md +1274 -0
  110. package/content/container-blocks/watermark.md +515 -0
  111. package/content/controls/for.md +103 -0
  112. package/content/controls/if.md +107 -0
  113. package/content/controls/log.md +50 -0
  114. package/content/controls/parallel.md +71 -0
  115. package/content/controls/parallel_for.md +36 -0
  116. package/content/controls/reject.md +56 -0
  117. package/content/controls/return.md +66 -0
  118. package/content/controls/set_state.md +98 -0
  119. package/content/controls/switch.md +90 -0
  120. package/content/controls/throw.md +79 -0
  121. package/content/controls/try.md +88 -0
  122. package/content/deployment/docker.md +149 -0
  123. package/content/deployment/node-server.md +27 -0
  124. package/content/deployment/vercel.md +101 -0
  125. package/content/display-blocks/aggrid.md +1459 -0
  126. package/content/display-blocks/anchor.md +336 -0
  127. package/content/display-blocks/avatar.md +1154 -0
  128. package/content/display-blocks/breadcrumb.md +517 -0
  129. package/content/display-blocks/button.md +758 -0
  130. package/content/display-blocks/calendar.md +156 -0
  131. package/content/display-blocks/dangeroushtml.md +314 -0
  132. package/content/display-blocks/dangerousmarkdown.md +423 -0
  133. package/content/display-blocks/diffgit.md +44 -0
  134. package/content/display-blocks/difflist.md +225 -0
  135. package/content/display-blocks/diffsidebyside.md +56 -0
  136. package/content/display-blocks/difftimeline.md +94 -0
  137. package/content/display-blocks/divider.md +406 -0
  138. package/content/display-blocks/download.md +60 -0
  139. package/content/display-blocks/dropdownbutton.md +661 -0
  140. package/content/display-blocks/echart.md +418 -0
  141. package/content/display-blocks/floatbutton.md +545 -0
  142. package/content/display-blocks/html.md +311 -0
  143. package/content/display-blocks/icon.md +717 -0
  144. package/content/display-blocks/img.md +479 -0
  145. package/content/display-blocks/markdown.md +520 -0
  146. package/content/display-blocks/markdownwithcode.md +730 -0
  147. package/content/display-blocks/menu.md +1123 -0
  148. package/content/display-blocks/message.md +941 -0
  149. package/content/display-blocks/notification.md +1410 -0
  150. package/content/display-blocks/paragraph.md +643 -0
  151. package/content/display-blocks/progress.md +818 -0
  152. package/content/display-blocks/qrcode.md +519 -0
  153. package/content/display-blocks/s3download.md +240 -0
  154. package/content/display-blocks/search.md +360 -0
  155. package/content/display-blocks/skeleton.md +272 -0
  156. package/content/display-blocks/skeletonavatar.md +222 -0
  157. package/content/display-blocks/skeletonbutton.md +245 -0
  158. package/content/display-blocks/skeletoninput.md +223 -0
  159. package/content/display-blocks/skeletonparagraph.md +201 -0
  160. package/content/display-blocks/statistic.md +485 -0
  161. package/content/display-blocks/steps.md +585 -0
  162. package/content/display-blocks/tag.md +620 -0
  163. package/content/display-blocks/title.md +654 -0
  164. package/content/display-blocks/tour.md +2359 -0
  165. package/content/input-blocks/autocomplete.md +786 -0
  166. package/content/input-blocks/buttonselector.md +964 -0
  167. package/content/input-blocks/checkboxselector.md +765 -0
  168. package/content/input-blocks/checkboxswitch.md +497 -0
  169. package/content/input-blocks/colorselector.md +884 -0
  170. package/content/input-blocks/daterangeselector.md +777 -0
  171. package/content/input-blocks/dateselector.md +693 -0
  172. package/content/input-blocks/datetimeselector.md +791 -0
  173. package/content/input-blocks/inputgallery.md +528 -0
  174. package/content/input-blocks/monthselector.md +717 -0
  175. package/content/input-blocks/multipleselector.md +1337 -0
  176. package/content/input-blocks/numberinput.md +855 -0
  177. package/content/input-blocks/pagination.md +679 -0
  178. package/content/input-blocks/paragraphinput.md +1664 -0
  179. package/content/input-blocks/passwordinput.md +480 -0
  180. package/content/input-blocks/phonenumberinput.md +688 -0
  181. package/content/input-blocks/radioselector.md +820 -0
  182. package/content/input-blocks/ratingslider.md +684 -0
  183. package/content/input-blocks/s3uploadbutton.md +384 -0
  184. package/content/input-blocks/s3uploaddragger.md +305 -0
  185. package/content/input-blocks/s3uploadphoto.md +288 -0
  186. package/content/input-blocks/segmentedselector.md +782 -0
  187. package/content/input-blocks/selector.md +1162 -0
  188. package/content/input-blocks/switch.md +552 -0
  189. package/content/input-blocks/textarea.md +714 -0
  190. package/content/input-blocks/textinput.md +639 -0
  191. package/content/input-blocks/tiptapinput.md +226 -0
  192. package/content/input-blocks/tiptapmentioninput.md +471 -0
  193. package/content/input-blocks/titleinput.md +1094 -0
  194. package/content/input-blocks/treeinput.md +144 -0
  195. package/content/input-blocks/treemultipleselector.md +162 -0
  196. package/content/input-blocks/treeselector.md +185 -0
  197. package/content/input-blocks/upload.md +149 -0
  198. package/content/input-blocks/uploaddragger.md +85 -0
  199. package/content/input-blocks/uploadphoto.md +84 -0
  200. package/content/input-blocks/weekselector.md +630 -0
  201. package/content/list-blocks/controlledlist.md +498 -0
  202. package/content/list-blocks/list.md +529 -0
  203. package/content/list-blocks/listselector.md +301 -0
  204. package/content/list-blocks/masonrylist.md +230 -0
  205. package/content/migration/upgrade-guide.md +106 -0
  206. package/content/migration/v4-to-v5.md +589 -0
  207. package/content/migration/v5-to-v6.md +36 -0
  208. package/content/operators/_actions.md +58 -0
  209. package/content/operators/_and.md +76 -0
  210. package/content/operators/_api.md +254 -0
  211. package/content/operators/_app.md +114 -0
  212. package/content/operators/_args.md +65 -0
  213. package/content/operators/_array.md +331 -0
  214. package/content/operators/_base64.md +49 -0
  215. package/content/operators/_boolean.md +33 -0
  216. package/content/operators/_build.md +166 -0
  217. package/content/operators/_change_case.md +357 -0
  218. package/content/operators/_date.md +415 -0
  219. package/content/operators/_dayjs.md +128 -0
  220. package/content/operators/_diff.md +61 -0
  221. package/content/operators/_divide.md +32 -0
  222. package/content/operators/_eq.md +52 -0
  223. package/content/operators/_event.md +86 -0
  224. package/content/operators/_function.md +52 -0
  225. package/content/operators/_get.md +92 -0
  226. package/content/operators/_global.md +86 -0
  227. package/content/operators/_gt.md +50 -0
  228. package/content/operators/_gte.md +50 -0
  229. package/content/operators/_hash.md +112 -0
  230. package/content/operators/_if.md +30 -0
  231. package/content/operators/_if_none.md +47 -0
  232. package/content/operators/_index.md +48 -0
  233. package/content/operators/_input.md +86 -0
  234. package/content/operators/_intl.md +135 -0
  235. package/content/operators/_item.md +166 -0
  236. package/content/operators/_js.md +113 -0
  237. package/content/operators/_json.md +120 -0
  238. package/content/operators/_jsonata.md +152 -0
  239. package/content/operators/_locale.md +65 -0
  240. package/content/operators/_location.md +40 -0
  241. package/content/operators/_log.md +22 -0
  242. package/content/operators/_lt.md +50 -0
  243. package/content/operators/_lte.md +50 -0
  244. package/content/operators/_math.md +407 -0
  245. package/content/operators/_media.md +83 -0
  246. package/content/operators/_menu.md +62 -0
  247. package/content/operators/_module.md +340 -0
  248. package/content/operators/_mql.md +95 -0
  249. package/content/operators/_ne.md +62 -0
  250. package/content/operators/_not.md +33 -0
  251. package/content/operators/_number.md +159 -0
  252. package/content/operators/_nunjucks.md +117 -0
  253. package/content/operators/_object.md +253 -0
  254. package/content/operators/_operator.md +33 -0
  255. package/content/operators/_or.md +67 -0
  256. package/content/operators/_product.md +45 -0
  257. package/content/operators/_random.md +90 -0
  258. package/content/operators/_ref.md +80 -0
  259. package/content/operators/_regex.md +69 -0
  260. package/content/operators/_request.md +35 -0
  261. package/content/operators/_request_details.md +29 -0
  262. package/content/operators/_secret.md +56 -0
  263. package/content/operators/_state.md +134 -0
  264. package/content/operators/_step.md +98 -0
  265. package/content/operators/_string.md +327 -0
  266. package/content/operators/_subtract.md +24 -0
  267. package/content/operators/_sum.md +45 -0
  268. package/content/operators/_switch.md +37 -0
  269. package/content/operators/_t.md +96 -0
  270. package/content/operators/_theme.md +65 -0
  271. package/content/operators/_type.md +78 -0
  272. package/content/operators/_uri.md +49 -0
  273. package/content/operators/_url_query.md +92 -0
  274. package/content/operators/_user.md +154 -0
  275. package/content/operators/_uuid.md +118 -0
  276. package/content/operators/_var.md +88 -0
  277. package/content/operators/_yaml.md +96 -0
  278. package/content/other/introduction.md +39 -0
  279. package/content/other/next-auth-providers.md +3 -0
  280. package/content/other/updatesession.md +26 -0
  281. package/content/plugins/plugins-actions.md +80 -0
  282. package/content/plugins/plugins-connections.md +98 -0
  283. package/content/plugins/plugins-dev.md +138 -0
  284. package/content/plugins/plugins-introduction.md +29 -0
  285. package/content/plugins/plugins-operators.md +71 -0
  286. package/content/testing/e2e-auth.md +108 -0
  287. package/content/testing/e2e-configuration.md +107 -0
  288. package/content/testing/e2e-getting-started.md +96 -0
  289. package/content/testing/e2e-introduction.md +53 -0
  290. package/content/testing/e2e-mocking.md +155 -0
  291. package/content/testing/e2e-writing-tests.md +214 -0
  292. package/content/tutorial/tutorial-actions-operators.md +222 -0
  293. package/content/tutorial/tutorial-add-blocks.md +86 -0
  294. package/content/tutorial/tutorial-create-page.md +57 -0
  295. package/content/tutorial/tutorial-display-data-page.md +181 -0
  296. package/content/tutorial/tutorial-next-steps.md +53 -0
  297. package/content/tutorial/tutorial-requests-sql.md +22 -0
  298. package/content/tutorial/tutorial-start.md +71 -0
  299. package/content/user-authentication/auth-providers.md +15 -0
  300. package/content/user-authentication/login-and-logout.md +122 -0
  301. package/content/user-authentication/users-introduction.md +31 -0
  302. package/index.json +2281 -0
  303. package/package.json +34 -0
  304. package/scripts/extractAgentDocs.js +130 -0
@@ -0,0 +1,96 @@
1
+ # Getting Started
2
+
3
+ ## Setup
4
+
5
+ The fastest way to set up e2e testing is with the scaffold command. Run this in your project root:
6
+
7
+ ```
8
+ npx @lowdefy/e2e-utils
9
+ ```
10
+
11
+ The command will:
12
+ - Detect your Lowdefy app (single-app or multi-app)
13
+ - Ask if you want MongoDB testing support
14
+ - Create test files in an `e2e/` directory
15
+ - Update your `package.json` with test scripts
16
+ - Update `.gitignore` with test artifacts
17
+ - Optionally install dependencies and Playwright browsers
18
+
19
+ ## Generated files
20
+
21
+ After running the scaffold command, your project will have:
22
+
23
+ ```
24
+ e2e/
25
+ playwright.config.js # Playwright configuration
26
+ fixtures.js # Test fixtures (provides the ldf helper)
27
+ mocks.yaml # Static request mocks
28
+ example.spec.js # Example test to get you started
29
+ ```
30
+
31
+ ## Running tests
32
+
33
+ Run all tests:
34
+ ```
35
+ pnpm e2e
36
+ ```
37
+
38
+ Run tests with the Playwright UI (useful for debugging):
39
+ ```
40
+ pnpm e2e:ui
41
+ ```
42
+
43
+ > The first run will take longer because Playwright needs to build and start your Lowdefy app. Subsequent runs reuse the server if it is still running.
44
+
45
+ ## Your first test
46
+
47
+ The generated `example.spec.js` looks like this:
48
+
49
+ ```javascript
50
+ import { test, expect } from './fixtures.js';
51
+
52
+ test('homepage loads', async ({ ldf }) => {
53
+ await ldf.goto('/');
54
+ await expect(ldf.page).toHaveTitle(/./);
55
+ });
56
+ ```
57
+
58
+ Let's break this down:
59
+
60
+ - `import { test, expect } from './fixtures.js'` — imports the enhanced test function that provides the `ldf` helper
61
+ - `test('...', async ({ ldf }) => { ... })` — the `ldf` object is automatically created for each test with a fresh browser page
62
+ - `await ldf.goto('/')` — navigates to the homepage and waits for the Lowdefy client to initialize
63
+ - `await expect(ldf.page).toHaveTitle(/./)` — uses the raw Playwright page to assert the page has a title
64
+
65
+ ## Writing a real test
66
+
67
+ Here is a more practical example. Suppose you have a page with a search form:
68
+
69
+ ```javascript
70
+ import { test } from './fixtures.js';
71
+
72
+ test('search returns results', async ({ ldf }) => {
73
+ await ldf.goto('/search');
74
+
75
+ // Fill in the search input and click search
76
+ await ldf.block('search_input').do.fill('widget');
77
+ await ldf.block('search_btn').do.click();
78
+
79
+ // Wait for the request to complete
80
+ await ldf.request('search_products').expect.toFinish();
81
+
82
+ // Check that results appeared in state
83
+ const results = await ldf.state('search_results').value();
84
+ expect(results.length).toBeGreaterThan(0);
85
+ });
86
+ ```
87
+
88
+ ## What happens behind the scenes
89
+
90
+ When you run `pnpm e2e`:
91
+
92
+ 1. Playwright reads `playwright.config.js`, which uses `createConfig()` from `@lowdefy/e2e-utils/config`
93
+ 2. The config starts a web server that builds your app with the e2e server variant and starts it
94
+ 3. A manifest is generated from your build artifacts, mapping block IDs to their types and e2e helpers
95
+ 4. For each test, a fresh `ldf` object is created with a new browser page, and any static mocks from `mocks.yaml` are applied
96
+ 5. Your test code runs against the live app
@@ -0,0 +1,53 @@
1
+ # E2E Testing
2
+
3
+ End-to-end (e2e) testing verifies that your Lowdefy app works correctly from the user's perspective. Instead of testing individual functions, e2e tests open a real browser, navigate through your pages, fill in forms, click buttons, and check that everything behaves as expected.
4
+
5
+ The `@lowdefy/e2e-utils` package provides a Playwright-based testing toolkit built specifically for Lowdefy apps. It understands Lowdefy's block system, state management, and request lifecycle, so you can write expressive tests without worrying about implementation details.
6
+
7
+ ## What it provides
8
+
9
+ - **Locator-first API** — an intuitive pattern: get the thing, then do something with it
10
+ - **Request mocking** — control request responses with static YAML files or inline overrides
11
+ - **Block helpers** — type-aware interactions that know how to fill a TextInput, click a Button, or select from a Selector
12
+ - **User authentication** — set test users per browser context with cookie-based session injection
13
+ - **Scaffold command** — `npx @lowdefy/e2e-utils` sets up everything you need in seconds
14
+
15
+ ## How it works
16
+
17
+ When you run your tests, Playwright:
18
+
19
+ 1. Builds your Lowdefy app as a production build
20
+ 2. Starts a local server
21
+ 3. Opens a browser and runs your test code against the live app
22
+
23
+ The e2e server build exposes internal state on `window.lowdefy`, so your tests can assert on page state, request responses, and validation status.
24
+
25
+ ## Prerequisites
26
+
27
+ - **Node.js 22+**
28
+ - A working Lowdefy app (your app must build successfully)
29
+ - **Playwright** (installed automatically during setup)
30
+
31
+ > E2e tests run against a production build of your app. If your app has build errors, the tests will not be able to start. Make sure <code>npx lowdefy build</code> succeeds before running tests.
32
+
33
+ ## Quick example
34
+
35
+ Here is what a simple test looks like:
36
+
37
+ ```javascript
38
+ import { test } from './fixtures.js';
39
+
40
+ test('contact form submits successfully', async ({ ldf }) => {
41
+ await ldf.goto('/contact');
42
+
43
+ await ldf.block('name_input').do.fill('Jane Doe');
44
+ await ldf.block('email_input').do.fill('jane@example.com');
45
+ await ldf.block('submit_btn').do.click();
46
+
47
+ await ldf.request('submit_form').expect.toFinish();
48
+ await ldf.state('form_submitted').expect.toBe(true);
49
+ await ldf.url().expect.toBe('/thank-you');
50
+ });
51
+ ```
52
+
53
+ The `ldf` object is your main testing helper. It is provided automatically by the test fixtures and gives you access to blocks, requests, state, and URL assertions.
@@ -0,0 +1,155 @@
1
+ # Mocking Requests
2
+
3
+ Some requests in your app may depend on external services that are unavailable in a test environment — for example, Atlas Search, third-party APIs, or services that require production credentials. Request mocking lets you control what these requests return, so your tests are fast, reliable, and independent of external systems.
4
+
5
+ There are two ways to mock requests:
6
+
7
+ - **Static mocks** — defined in `mocks.yaml`, applied to every test automatically
8
+ - **Inline mocks** — defined per-test in your test code, for test-specific scenarios
9
+
10
+ ## Static mocks (mocks.yaml)
11
+
12
+ The `mocks.yaml` file in your `e2e/` directory defines mocks that apply to all tests. This is useful for requests that should always be mocked in the test environment.
13
+
14
+ ```yaml
15
+ requests:
16
+ - requestId: atlas_search
17
+ response:
18
+ - _id: doc-1
19
+ title: Search Result
20
+ score: 0.95
21
+
22
+ - requestId: fetch_config
23
+ response:
24
+ theme: dark
25
+ features:
26
+ - analytics
27
+ - notifications
28
+ ```
29
+
30
+ ### Page-specific mocks
31
+
32
+ You can scope a mock to a specific page using `pageId`:
33
+
34
+ ```yaml
35
+ requests:
36
+ - requestId: fetch_data
37
+ pageId: admin-dashboard
38
+ response: []
39
+ ```
40
+
41
+ ### Wildcard patterns
42
+
43
+ Use `*` wildcards to match multiple request or page IDs:
44
+
45
+ ```yaml
46
+ requests:
47
+ # Match any request starting with "fetch_"
48
+ - requestId: fetch_*
49
+ response: []
50
+
51
+ # Match fetch_* only on pages starting with "admin-"
52
+ - requestId: fetch_*
53
+ pageId: admin-*
54
+ response: []
55
+ ```
56
+
57
+ ### Error mocks
58
+
59
+ You can simulate request errors:
60
+
61
+ ```yaml
62
+ requests:
63
+ - requestId: atlas_search
64
+ error: Atlas Search is not available in the test environment
65
+ ```
66
+
67
+ ### API endpoint mocks
68
+
69
+ You can also mock Lowdefy API endpoints:
70
+
71
+ ```yaml
72
+ api:
73
+ - endpointId: external_webhook
74
+ method: POST
75
+ response:
76
+ status: ok
77
+ message: Webhook received
78
+ ```
79
+
80
+ ## Inline mocks (per-test)
81
+
82
+ Use `ldf.mock.request()` and `ldf.mock.api()` to set up mocks for individual tests. Inline mocks override static mocks for the same request ID.
83
+
84
+ ### Mock a request response
85
+
86
+ ```javascript
87
+ test('handles empty search results', async ({ ldf }) => {
88
+ await ldf.mock.request('search_products', {
89
+ response: [],
90
+ });
91
+
92
+ await ldf.goto('/search');
93
+ await ldf.block('search_input').do.fill('nonexistent');
94
+ await ldf.block('search_btn').do.click();
95
+
96
+ await ldf.request('search_products').expect.toFinish();
97
+ await ldf.block('no_results_alert').expect.visible();
98
+ });
99
+ ```
100
+
101
+ ### Simulate an error
102
+
103
+ ```javascript
104
+ test('shows error message when request fails', async ({ ldf }) => {
105
+ await ldf.mock.request('save_user', {
106
+ error: 'Database connection failed',
107
+ });
108
+
109
+ await ldf.goto('/profile');
110
+ await ldf.block('save_btn').do.click();
111
+
112
+ // Your app should handle the error gracefully
113
+ });
114
+ ```
115
+
116
+ ### Mock an API endpoint
117
+
118
+ ```javascript
119
+ await ldf.mock.api('external_webhook', {
120
+ method: 'POST',
121
+ response: { status: 'ok' },
122
+ });
123
+ ```
124
+
125
+ When you specify `method`, only that HTTP method is intercepted. Other methods pass through to the real server.
126
+
127
+ ## Capturing request payloads
128
+
129
+ Mocked requests automatically capture the payload sent by your app. This is useful for asserting that the correct data was sent:
130
+
131
+ ```javascript
132
+ test('search sends correct payload', async ({ ldf }) => {
133
+ await ldf.mock.request('search_products', { response: [] });
134
+
135
+ await ldf.goto('/search');
136
+ await ldf.block('search_input').do.fill('widget');
137
+ await ldf.block('search_btn').do.click();
138
+
139
+ const captured = ldf.mock.getCapturedRequest('search_products');
140
+ expect(captured.payload).toEqual(
141
+ expect.objectContaining({ query: 'widget' })
142
+ );
143
+ });
144
+ ```
145
+
146
+ Use `ldf.mock.clearCapturedRequests()` to clear captured payloads between assertions if needed.
147
+
148
+ ## Mock priority
149
+
150
+ When both static and inline mocks exist for the same request:
151
+
152
+ 1. Static mocks from `mocks.yaml` are applied first (during test setup)
153
+ 2. Inline mocks override static mocks for the same request ID
154
+
155
+ This means you can set sensible defaults in `mocks.yaml` and override them in specific tests.
@@ -0,0 +1,214 @@
1
+ # Writing Tests
2
+
3
+ The `ldf` object is your main interface for testing Lowdefy apps. It is provided by the test fixtures and gives you a locator-first API — you get the thing you want to interact with, then perform actions or assertions on it.
4
+
5
+ ## Navigation
6
+
7
+ Navigate to a page and wait for Lowdefy to be ready:
8
+
9
+ ```javascript
10
+ await ldf.goto('/my-page');
11
+ ```
12
+
13
+ If your test triggers a page navigation (for example, a Link action), you can wait for the new page:
14
+
15
+ ```javascript
16
+ await ldf.block('next_btn').do.click();
17
+ await ldf.waitForPage('/next-page');
18
+ ```
19
+
20
+ You can also access the current page ID and the raw Playwright page object:
21
+
22
+ ```javascript
23
+ const pageId = ldf.pageId; // e.g. 'contact-form'
24
+ const playwrightPage = ldf.page; // for advanced Playwright usage
25
+ ```
26
+
27
+ ## Block interactions
28
+
29
+ Use `ldf.block('blockId')` to get a block, then call methods on it.
30
+
31
+ ### Actions (`do`)
32
+
33
+ The available `do` methods depend on the block type. Common examples:
34
+
35
+ ```javascript
36
+ // TextInput - fill, clear
37
+ await ldf.block('name_input').do.fill('John Doe');
38
+ await ldf.block('name_input').do.clear();
39
+
40
+ // Button - click
41
+ await ldf.block('submit_btn').do.click();
42
+
43
+ // Selector - select an option
44
+ await ldf.block('category').do.select('Support');
45
+
46
+ // NumberInput - fill with a number
47
+ await ldf.block('quantity').do.fill('5');
48
+
49
+ // TextArea - fill with text
50
+ await ldf.block('comments').do.fill('This is a comment');
51
+ ```
52
+
53
+ ### Assertions (`expect`)
54
+
55
+ Every block has these built-in assertion methods:
56
+
57
+ ```javascript
58
+ await ldf.block('submit_btn').expect.visible();
59
+ await ldf.block('submit_btn').expect.hidden();
60
+ await ldf.block('submit_btn').expect.disabled();
61
+ await ldf.block('submit_btn').expect.enabled();
62
+ ```
63
+
64
+ Block types can also provide their own assertions:
65
+
66
+ ```javascript
67
+ // TextInput - check value and placeholder
68
+ await ldf.block('name_input').expect.value('John Doe');
69
+ await ldf.block('name_input').expect.placeholder('Enter your name');
70
+
71
+ // Button - check text
72
+ await ldf.block('submit_btn').expect.text('Submit');
73
+ ```
74
+
75
+ > The available <code>do</code> and <code>expect</code> methods depend on the block type. If you call a method that does not exist, you will get a clear error listing the available methods.
76
+
77
+ ### Raw locator access
78
+
79
+ If you need to do something not covered by the block helpers, you can get the raw Playwright locator:
80
+
81
+ ```javascript
82
+ const locator = ldf.block('my_block').locator();
83
+ await locator.scrollIntoViewIfNeeded();
84
+ await locator.screenshot({ path: 'block.png' });
85
+ ```
86
+
87
+ ## State
88
+
89
+ Use `ldf.state('key')` to interact with page state.
90
+
91
+ ### Assert state values
92
+
93
+ ```javascript
94
+ await ldf.state('form_submitted').expect.toBe(true);
95
+ await ldf.state('selected_items').expect.toBe(['item1', 'item2']);
96
+ ```
97
+
98
+ ### Set state programmatically
99
+
100
+ ```javascript
101
+ await ldf.state('search_query').do.set('widget');
102
+ ```
103
+
104
+ ### Read state values
105
+
106
+ ```javascript
107
+ // Get a specific state value
108
+ const name = await ldf.state('name_input').value();
109
+
110
+ // Get the entire page state
111
+ const allState = await ldf.state().value();
112
+ ```
113
+
114
+ You can use dot notation for nested state:
115
+
116
+ ```javascript
117
+ await ldf.state('form.email').expect.toBe('jane@example.com');
118
+ ```
119
+
120
+ ## Requests
121
+
122
+ Use `ldf.request('requestId')` to assert on request state and responses.
123
+
124
+ ### Wait for a request to complete
125
+
126
+ ```javascript
127
+ await ldf.request('fetch_users').expect.toFinish();
128
+ ```
129
+
130
+ ### Assert the response
131
+
132
+ ```javascript
133
+ await ldf.request('fetch_users').expect.toHaveResponse([
134
+ { id: 1, name: 'Alice' },
135
+ { id: 2, name: 'Bob' },
136
+ ]);
137
+ ```
138
+
139
+ `toHaveResponse` uses an exact match — the actual response must be identical to the expected value.
140
+
141
+ ### Assert the payload
142
+
143
+ ```javascript
144
+ await ldf.request('search').expect.toHavePayload({ query: 'widget' });
145
+ ```
146
+
147
+ `toHavePayload` uses a partial match — the actual payload only needs to contain the keys you specify. It can have additional keys.
148
+
149
+ ### Read the raw response
150
+
151
+ ```javascript
152
+ const data = await ldf.request('fetch_users').response();
153
+ ```
154
+
155
+ ## URL
156
+
157
+ Use `ldf.url()` and `ldf.urlQuery('key')` for URL assertions.
158
+
159
+ ### Assert the current URL
160
+
161
+ ```javascript
162
+ await ldf.url().expect.toBe('/dashboard');
163
+ await ldf.url().expect.toMatch(/dashboard/);
164
+ ```
165
+
166
+ ### Work with query parameters
167
+
168
+ ```javascript
169
+ // Assert a query parameter
170
+ await ldf.urlQuery('tab').expect.toBe('settings');
171
+
172
+ // Set a query parameter
173
+ await ldf.urlQuery('page').do.set('2');
174
+
175
+ // Read a query parameter
176
+ const tab = ldf.urlQuery('tab').value();
177
+ ```
178
+
179
+ ## Validation
180
+
181
+ You can assert on block validation status using the built-in validation methods:
182
+
183
+ ```javascript
184
+ // Trigger validation by clicking submit
185
+ await ldf.block('submit_btn').do.click();
186
+
187
+ // Check that a block has a validation error
188
+ await ldf.block('email_input').expect.validationError();
189
+
190
+ // Check for a specific error message
191
+ await ldf.block('email_input').expect.validationError({
192
+ message: 'Please enter a valid email address',
193
+ });
194
+
195
+ // Check for warnings and success
196
+ await ldf.block('age_input').expect.validationWarning();
197
+ await ldf.block('name_input').expect.validationSuccess();
198
+ ```
199
+
200
+ ## Timeouts
201
+
202
+ Assertion methods accept an optional `{ timeout }` parameter (in milliseconds):
203
+
204
+ ```javascript
205
+ // Wait up to 60 seconds for a slow request
206
+ await ldf.request('generate_report').expect.toFinish({ timeout: 60000 });
207
+
208
+ // Wait up to 10 seconds for state to update
209
+ await ldf.state('processed').expect.toBe(true, { timeout: 10000 });
210
+ ```
211
+
212
+ Default timeouts:
213
+ - Request assertions: 30 seconds
214
+ - State, URL, and validation assertions: 5 seconds