@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.
- package/LICENSE +201 -0
- package/README.md +20 -0
- package/content/actions/callapi.md +66 -0
- package/content/actions/callmethod.md +41 -0
- package/content/actions/copytoclipboard.md +32 -0
- package/content/actions/displaymessage.md +53 -0
- package/content/actions/downloadcsv.md +89 -0
- package/content/actions/fetch.md +50 -0
- package/content/actions/geolocationcurrentposition.md +56 -0
- package/content/actions/link.md +104 -0
- package/content/actions/login.md +85 -0
- package/content/actions/logout.md +56 -0
- package/content/actions/pdfmake.md +304 -0
- package/content/actions/request.md +67 -0
- package/content/actions/reset.md +27 -0
- package/content/actions/resetvalidation.md +85 -0
- package/content/actions/scrollto.md +85 -0
- package/content/actions/setdarkmode.md +62 -0
- package/content/actions/setfocus.md +33 -0
- package/content/actions/setglobal.md +63 -0
- package/content/actions/setlocale.md +69 -0
- package/content/actions/setstate.md +70 -0
- package/content/actions/throw.md +72 -0
- package/content/actions/validate.md +87 -0
- package/content/actions/wait.md +24 -0
- package/content/agents/agent-endpoint-tools.md +190 -0
- package/content/agents/agent-mcp-tools.md +124 -0
- package/content/agents/agent-multi-agent.md +136 -0
- package/content/agents/agent-properties.md +174 -0
- package/content/agents/agent-server-hooks.md +336 -0
- package/content/agents/agentchat.md +515 -0
- package/content/agents/agentconversations.md +148 -0
- package/content/concepts/ai-agent-docs.md +190 -0
- package/content/concepts/cli.md +213 -0
- package/content/concepts/connections-and-requests.md +7 -0
- package/content/concepts/custom-html.md +3 -0
- package/content/concepts/custom-styling.md +161 -0
- package/content/concepts/events-and-actions.md +271 -0
- package/content/concepts/hosting-files.md +18 -0
- package/content/concepts/i18n.md +320 -0
- package/content/concepts/keyboard-shortcuts.md +218 -0
- package/content/concepts/layout-overview.md +478 -0
- package/content/concepts/lists.md +144 -0
- package/content/concepts/logger.md +43 -0
- package/content/concepts/lowdefy-schema.md +365 -0
- package/content/concepts/menus.md +144 -0
- package/content/concepts/module-authoring.md +564 -0
- package/content/concepts/modules.md +338 -0
- package/content/concepts/operators.md +45 -0
- package/content/concepts/overview.md +19 -0
- package/content/concepts/references-and-templates.md +207 -0
- package/content/concepts/secrets.md +24 -0
- package/content/concepts/selectors.md +143 -0
- package/content/connections/amazonredshift.md +176 -0
- package/content/connections/anthropic.md +232 -0
- package/content/connections/awss3.md +313 -0
- package/content/connections/azureblobstorage.md +133 -0
- package/content/connections/elasticsearch.md +371 -0
- package/content/connections/google.md +228 -0
- package/content/connections/googlecloudstorage.md +139 -0
- package/content/connections/googlesheet.md +313 -0
- package/content/connections/knex.md +206 -0
- package/content/connections/mariadb.md +177 -0
- package/content/connections/mcp.md +134 -0
- package/content/connections/mongodb.md +739 -0
- package/content/connections/mssql.md +191 -0
- package/content/connections/mysql.md +192 -0
- package/content/connections/openai-connection.md +228 -0
- package/content/connections/postgresql.md +200 -0
- package/content/connections/redis.md +116 -0
- package/content/connections/sendgridmail.md +94 -0
- package/content/connections/smtp.md +149 -0
- package/content/connections/sqlite.md +187 -0
- package/content/connections/stripe.md +115 -0
- package/content/container-blocks/affix.md +413 -0
- package/content/container-blocks/alert.md +937 -0
- package/content/container-blocks/badge.md +1222 -0
- package/content/container-blocks/box.md +1453 -0
- package/content/container-blocks/card.md +1200 -0
- package/content/container-blocks/carousel.md +2247 -0
- package/content/container-blocks/collapse.md +1335 -0
- package/content/container-blocks/configprovider.md +1070 -0
- package/content/container-blocks/confirmmodal.md +1159 -0
- package/content/container-blocks/content.md +597 -0
- package/content/container-blocks/descriptions.md +870 -0
- package/content/container-blocks/drawer.md +1379 -0
- package/content/container-blocks/dropdownmenu.md +643 -0
- package/content/container-blocks/flex.md +1510 -0
- package/content/container-blocks/footer.md +380 -0
- package/content/container-blocks/googlemaps.md +546 -0
- package/content/container-blocks/googlemapsheatmap.md +607 -0
- package/content/container-blocks/googlemapsscript.md +61 -0
- package/content/container-blocks/header.md +302 -0
- package/content/container-blocks/label.md +739 -0
- package/content/container-blocks/layout.md +604 -0
- package/content/container-blocks/masonry.md +1090 -0
- package/content/container-blocks/mobilemenu.md +1244 -0
- package/content/container-blocks/modal.md +1180 -0
- package/content/container-blocks/pageheadermenu.md +614 -0
- package/content/container-blocks/pagesidebarlayout.md +438 -0
- package/content/container-blocks/pagesidermenu.md +651 -0
- package/content/container-blocks/popover.md +1421 -0
- package/content/container-blocks/result.md +972 -0
- package/content/container-blocks/sider.md +1064 -0
- package/content/container-blocks/span.md +747 -0
- package/content/container-blocks/spinner.md +267 -0
- package/content/container-blocks/splitter.md +1735 -0
- package/content/container-blocks/tabs.md +1718 -0
- package/content/container-blocks/tooltip.md +1274 -0
- package/content/container-blocks/watermark.md +515 -0
- package/content/controls/for.md +103 -0
- package/content/controls/if.md +107 -0
- package/content/controls/log.md +50 -0
- package/content/controls/parallel.md +71 -0
- package/content/controls/parallel_for.md +36 -0
- package/content/controls/reject.md +56 -0
- package/content/controls/return.md +66 -0
- package/content/controls/set_state.md +98 -0
- package/content/controls/switch.md +90 -0
- package/content/controls/throw.md +79 -0
- package/content/controls/try.md +88 -0
- package/content/deployment/docker.md +149 -0
- package/content/deployment/node-server.md +27 -0
- package/content/deployment/vercel.md +101 -0
- package/content/display-blocks/aggrid.md +1459 -0
- package/content/display-blocks/anchor.md +336 -0
- package/content/display-blocks/avatar.md +1154 -0
- package/content/display-blocks/breadcrumb.md +517 -0
- package/content/display-blocks/button.md +758 -0
- package/content/display-blocks/calendar.md +156 -0
- package/content/display-blocks/dangeroushtml.md +314 -0
- package/content/display-blocks/dangerousmarkdown.md +423 -0
- package/content/display-blocks/diffgit.md +44 -0
- package/content/display-blocks/difflist.md +225 -0
- package/content/display-blocks/diffsidebyside.md +56 -0
- package/content/display-blocks/difftimeline.md +94 -0
- package/content/display-blocks/divider.md +406 -0
- package/content/display-blocks/download.md +60 -0
- package/content/display-blocks/dropdownbutton.md +661 -0
- package/content/display-blocks/echart.md +418 -0
- package/content/display-blocks/floatbutton.md +545 -0
- package/content/display-blocks/html.md +311 -0
- package/content/display-blocks/icon.md +717 -0
- package/content/display-blocks/img.md +479 -0
- package/content/display-blocks/markdown.md +520 -0
- package/content/display-blocks/markdownwithcode.md +730 -0
- package/content/display-blocks/menu.md +1123 -0
- package/content/display-blocks/message.md +941 -0
- package/content/display-blocks/notification.md +1410 -0
- package/content/display-blocks/paragraph.md +643 -0
- package/content/display-blocks/progress.md +818 -0
- package/content/display-blocks/qrcode.md +519 -0
- package/content/display-blocks/s3download.md +240 -0
- package/content/display-blocks/search.md +360 -0
- package/content/display-blocks/skeleton.md +272 -0
- package/content/display-blocks/skeletonavatar.md +222 -0
- package/content/display-blocks/skeletonbutton.md +245 -0
- package/content/display-blocks/skeletoninput.md +223 -0
- package/content/display-blocks/skeletonparagraph.md +201 -0
- package/content/display-blocks/statistic.md +485 -0
- package/content/display-blocks/steps.md +585 -0
- package/content/display-blocks/tag.md +620 -0
- package/content/display-blocks/title.md +654 -0
- package/content/display-blocks/tour.md +2359 -0
- package/content/input-blocks/autocomplete.md +786 -0
- package/content/input-blocks/buttonselector.md +964 -0
- package/content/input-blocks/checkboxselector.md +765 -0
- package/content/input-blocks/checkboxswitch.md +497 -0
- package/content/input-blocks/colorselector.md +884 -0
- package/content/input-blocks/daterangeselector.md +777 -0
- package/content/input-blocks/dateselector.md +693 -0
- package/content/input-blocks/datetimeselector.md +791 -0
- package/content/input-blocks/inputgallery.md +528 -0
- package/content/input-blocks/monthselector.md +717 -0
- package/content/input-blocks/multipleselector.md +1337 -0
- package/content/input-blocks/numberinput.md +855 -0
- package/content/input-blocks/pagination.md +679 -0
- package/content/input-blocks/paragraphinput.md +1664 -0
- package/content/input-blocks/passwordinput.md +480 -0
- package/content/input-blocks/phonenumberinput.md +688 -0
- package/content/input-blocks/radioselector.md +820 -0
- package/content/input-blocks/ratingslider.md +684 -0
- package/content/input-blocks/s3uploadbutton.md +384 -0
- package/content/input-blocks/s3uploaddragger.md +305 -0
- package/content/input-blocks/s3uploadphoto.md +288 -0
- package/content/input-blocks/segmentedselector.md +782 -0
- package/content/input-blocks/selector.md +1162 -0
- package/content/input-blocks/switch.md +552 -0
- package/content/input-blocks/textarea.md +714 -0
- package/content/input-blocks/textinput.md +639 -0
- package/content/input-blocks/tiptapinput.md +226 -0
- package/content/input-blocks/tiptapmentioninput.md +471 -0
- package/content/input-blocks/titleinput.md +1094 -0
- package/content/input-blocks/treeinput.md +144 -0
- package/content/input-blocks/treemultipleselector.md +162 -0
- package/content/input-blocks/treeselector.md +185 -0
- package/content/input-blocks/upload.md +149 -0
- package/content/input-blocks/uploaddragger.md +85 -0
- package/content/input-blocks/uploadphoto.md +84 -0
- package/content/input-blocks/weekselector.md +630 -0
- package/content/list-blocks/controlledlist.md +498 -0
- package/content/list-blocks/list.md +529 -0
- package/content/list-blocks/listselector.md +301 -0
- package/content/list-blocks/masonrylist.md +230 -0
- package/content/migration/upgrade-guide.md +106 -0
- package/content/migration/v4-to-v5.md +589 -0
- package/content/migration/v5-to-v6.md +36 -0
- package/content/operators/_actions.md +58 -0
- package/content/operators/_and.md +76 -0
- package/content/operators/_api.md +254 -0
- package/content/operators/_app.md +114 -0
- package/content/operators/_args.md +65 -0
- package/content/operators/_array.md +331 -0
- package/content/operators/_base64.md +49 -0
- package/content/operators/_boolean.md +33 -0
- package/content/operators/_build.md +166 -0
- package/content/operators/_change_case.md +357 -0
- package/content/operators/_date.md +415 -0
- package/content/operators/_dayjs.md +128 -0
- package/content/operators/_diff.md +61 -0
- package/content/operators/_divide.md +32 -0
- package/content/operators/_eq.md +52 -0
- package/content/operators/_event.md +86 -0
- package/content/operators/_function.md +52 -0
- package/content/operators/_get.md +92 -0
- package/content/operators/_global.md +86 -0
- package/content/operators/_gt.md +50 -0
- package/content/operators/_gte.md +50 -0
- package/content/operators/_hash.md +112 -0
- package/content/operators/_if.md +30 -0
- package/content/operators/_if_none.md +47 -0
- package/content/operators/_index.md +48 -0
- package/content/operators/_input.md +86 -0
- package/content/operators/_intl.md +135 -0
- package/content/operators/_item.md +166 -0
- package/content/operators/_js.md +113 -0
- package/content/operators/_json.md +120 -0
- package/content/operators/_jsonata.md +152 -0
- package/content/operators/_locale.md +65 -0
- package/content/operators/_location.md +40 -0
- package/content/operators/_log.md +22 -0
- package/content/operators/_lt.md +50 -0
- package/content/operators/_lte.md +50 -0
- package/content/operators/_math.md +407 -0
- package/content/operators/_media.md +83 -0
- package/content/operators/_menu.md +62 -0
- package/content/operators/_module.md +340 -0
- package/content/operators/_mql.md +95 -0
- package/content/operators/_ne.md +62 -0
- package/content/operators/_not.md +33 -0
- package/content/operators/_number.md +159 -0
- package/content/operators/_nunjucks.md +117 -0
- package/content/operators/_object.md +253 -0
- package/content/operators/_operator.md +33 -0
- package/content/operators/_or.md +67 -0
- package/content/operators/_product.md +45 -0
- package/content/operators/_random.md +90 -0
- package/content/operators/_ref.md +80 -0
- package/content/operators/_regex.md +69 -0
- package/content/operators/_request.md +35 -0
- package/content/operators/_request_details.md +29 -0
- package/content/operators/_secret.md +56 -0
- package/content/operators/_state.md +134 -0
- package/content/operators/_step.md +98 -0
- package/content/operators/_string.md +327 -0
- package/content/operators/_subtract.md +24 -0
- package/content/operators/_sum.md +45 -0
- package/content/operators/_switch.md +37 -0
- package/content/operators/_t.md +96 -0
- package/content/operators/_theme.md +65 -0
- package/content/operators/_type.md +78 -0
- package/content/operators/_uri.md +49 -0
- package/content/operators/_url_query.md +92 -0
- package/content/operators/_user.md +154 -0
- package/content/operators/_uuid.md +118 -0
- package/content/operators/_var.md +88 -0
- package/content/operators/_yaml.md +96 -0
- package/content/other/introduction.md +39 -0
- package/content/other/next-auth-providers.md +3 -0
- package/content/other/updatesession.md +26 -0
- package/content/plugins/plugins-actions.md +80 -0
- package/content/plugins/plugins-connections.md +98 -0
- package/content/plugins/plugins-dev.md +138 -0
- package/content/plugins/plugins-introduction.md +29 -0
- package/content/plugins/plugins-operators.md +71 -0
- package/content/testing/e2e-auth.md +108 -0
- package/content/testing/e2e-configuration.md +107 -0
- package/content/testing/e2e-getting-started.md +96 -0
- package/content/testing/e2e-introduction.md +53 -0
- package/content/testing/e2e-mocking.md +155 -0
- package/content/testing/e2e-writing-tests.md +214 -0
- package/content/tutorial/tutorial-actions-operators.md +222 -0
- package/content/tutorial/tutorial-add-blocks.md +86 -0
- package/content/tutorial/tutorial-create-page.md +57 -0
- package/content/tutorial/tutorial-display-data-page.md +181 -0
- package/content/tutorial/tutorial-next-steps.md +53 -0
- package/content/tutorial/tutorial-requests-sql.md +22 -0
- package/content/tutorial/tutorial-start.md +71 -0
- package/content/user-authentication/auth-providers.md +15 -0
- package/content/user-authentication/login-and-logout.md +122 -0
- package/content/user-authentication/users-introduction.md +31 -0
- package/index.json +2281 -0
- package/package.json +34 -0
- 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
|