@lowdefy/e2e-utils 0.0.0-experimental-20260311112138 → 4.7.0
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/dist/config.js +12 -2
- package/dist/core/escapeId.js +18 -0
- package/dist/core/locators.js +3 -2
- package/dist/index.js +2 -0
- package/dist/init/generateFiles.js +8 -0
- package/dist/init/index.js +4 -2
- package/dist/init/templates/README.md.template +50 -6
- package/dist/init/templates/example.spec.js.template +3 -5
- package/dist/init/templates/fixtures.js.template +3 -2
- package/dist/init/templates/playwright.config.js.template +1 -1
- package/dist/testPrep/generateManifest.js +8 -20
- package/package.json +6 -4
package/dist/config.js
CHANGED
|
@@ -40,7 +40,12 @@ function createConfig({ appDir = './', buildDir = '.lowdefy/server/build', comma
|
|
|
40
40
|
use: {
|
|
41
41
|
baseURL: `http://localhost:${port}`,
|
|
42
42
|
trace: 'on-first-retry',
|
|
43
|
-
screenshot
|
|
43
|
+
screenshot,
|
|
44
|
+
...process.env.SLOW_MO && {
|
|
45
|
+
launchOptions: {
|
|
46
|
+
slowMo: Number(process.env.SLOW_MO)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
44
49
|
},
|
|
45
50
|
projects: [
|
|
46
51
|
{
|
|
@@ -102,7 +107,12 @@ function createMultiAppConfig({ apps = [], commandPrefix = '', testDir = 'e2e',
|
|
|
102
107
|
outputDir,
|
|
103
108
|
use: {
|
|
104
109
|
trace: 'on-first-retry',
|
|
105
|
-
screenshot
|
|
110
|
+
screenshot,
|
|
111
|
+
...process.env.SLOW_MO && {
|
|
112
|
+
launchOptions: {
|
|
113
|
+
slowMo: Number(process.env.SLOW_MO)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
106
116
|
},
|
|
107
117
|
projects,
|
|
108
118
|
webServer
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ function escapeId(id) {
|
|
16
|
+
return id.replace(/([^\w-])/g, '\\$1');
|
|
17
|
+
}
|
|
18
|
+
export { escapeId };
|
package/dist/core/locators.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
15
|
+
*/ import { escapeId } from './escapeId.js';
|
|
16
|
+
function getBlock(page, blockId) {
|
|
17
|
+
return page.locator(`#bl-${escapeId(blockId)}`);
|
|
17
18
|
}
|
|
18
19
|
export { getBlock };
|
package/dist/index.js
CHANGED
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ // Core helpers
|
|
16
|
+
import { escapeId } from './core/escapeId.js';
|
|
16
17
|
import { getBlock } from './core/locators.js';
|
|
17
18
|
import { goto, waitForReady, expectNavigation, waitForPage } from './core/navigation.js';
|
|
18
19
|
import { getRequestState, getRequestResponse, expectRequest } from './core/requests.js';
|
|
19
20
|
import { getState, getBlockState, setState, expectState } from './core/state.js';
|
|
20
21
|
import { getValidation, expectValidationStatus, expectValidationError, expectValidationWarning, expectValidationSuccess } from './core/validation.js';
|
|
21
22
|
import { expectUrl, expectUrlQuery, setUrlQuery } from './core/url.js';
|
|
23
|
+
export { escapeId };
|
|
22
24
|
export { getBlock };
|
|
23
25
|
export { goto, waitForReady, expectNavigation, waitForPage };
|
|
24
26
|
export { getRequestState, getRequestResponse, expectRequest };
|
|
@@ -66,10 +66,18 @@ function updatePackageJson({ appPath, cwd, useExperimental, useMongoDB }) {
|
|
|
66
66
|
pkg.scripts.e2e = 'playwright test --config=e2e/playwright.config.js';
|
|
67
67
|
updated = true;
|
|
68
68
|
}
|
|
69
|
+
if (!pkg.scripts['e2e:headed']) {
|
|
70
|
+
pkg.scripts['e2e:headed'] = 'SLOW_MO=500 playwright test --headed --workers=1 --config=e2e/playwright.config.js';
|
|
71
|
+
updated = true;
|
|
72
|
+
}
|
|
69
73
|
if (!pkg.scripts['e2e:ui']) {
|
|
70
74
|
pkg.scripts['e2e:ui'] = 'playwright test --ui --config=e2e/playwright.config.js';
|
|
71
75
|
updated = true;
|
|
72
76
|
}
|
|
77
|
+
if (!pkg.scripts['e2e:server']) {
|
|
78
|
+
pkg.scripts['e2e:server'] = 'lowdefy build --server e2e && lowdefy start --port 3000 --log-level warn';
|
|
79
|
+
updated = true;
|
|
80
|
+
}
|
|
73
81
|
// Add devDependencies
|
|
74
82
|
const tag = useExperimental ? 'experimental' : 'latest';
|
|
75
83
|
pkg.devDependencies = pkg.devDependencies || {};
|
package/dist/init/index.js
CHANGED
|
@@ -142,8 +142,10 @@ async function init() {
|
|
|
142
142
|
const app = selectedApps[0];
|
|
143
143
|
console.log(`Run tests from ${app.path}:`);
|
|
144
144
|
console.log(` cd ${app.path}`);
|
|
145
|
-
console.log(' pnpm e2e');
|
|
146
|
-
console.log(' pnpm e2e:
|
|
145
|
+
console.log(' pnpm e2e # Run all tests');
|
|
146
|
+
console.log(' pnpm e2e:headed # Run with visible browser');
|
|
147
|
+
console.log(' pnpm e2e:ui # Playwright UI mode');
|
|
148
|
+
console.log(' pnpm e2e:server # Start server for reuse');
|
|
147
149
|
} else {
|
|
148
150
|
console.log('Run tests from each app directory:');
|
|
149
151
|
for (const app of selectedApps){
|
|
@@ -5,10 +5,32 @@ End-to-end tests for your Lowdefy app using Playwright.
|
|
|
5
5
|
## Running Tests
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm e2e
|
|
9
|
-
pnpm e2e:
|
|
8
|
+
pnpm e2e # Run all tests
|
|
9
|
+
pnpm e2e:headed # Run with visible browser (slow motion)
|
|
10
|
+
pnpm e2e:ui # Playwright UI mode (interactive)
|
|
11
|
+
pnpm e2e:server # Start server for reuse (skip rebuilds)
|
|
10
12
|
```
|
|
11
13
|
|
|
14
|
+
## Faster Test Runs
|
|
15
|
+
|
|
16
|
+
By default, `pnpm e2e` does a full build before running tests. To skip the build during development:
|
|
17
|
+
|
|
18
|
+
1. **Start the server once** in a separate terminal:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm e2e:server
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. **Run tests** in another terminal — the config has `reuseExistingServer: true`, so Playwright detects the running server and skips the build:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm e2e
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This saves 60-90 seconds per run. Keep the server running while iterating on tests.
|
|
31
|
+
|
|
32
|
+
> **Tip:** If port 3000 is already in use, change the port in both the `e2e:server` script in `package.json` and the `port` option in `playwright.config.js`.
|
|
33
|
+
|
|
12
34
|
## File Structure
|
|
13
35
|
|
|
14
36
|
```
|
|
@@ -136,6 +158,28 @@ await ldf.mock.request('fetch_user', {
|
|
|
136
158
|
});
|
|
137
159
|
```
|
|
138
160
|
|
|
161
|
+
## Common Patterns
|
|
162
|
+
|
|
163
|
+
### Testing Page Navigation
|
|
164
|
+
|
|
165
|
+
When a form submit navigates away from the page (e.g., `Link: back`, redirects), use `Promise.all` with `page.waitForURL()` and the click action:
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
test('submits form and navigates back', async ({ ldf, page }) => {
|
|
169
|
+
await ldf.goto('/my-form');
|
|
170
|
+
|
|
171
|
+
await ldf.block('name_input').do.fill('Test');
|
|
172
|
+
|
|
173
|
+
// Wait for navigation and click simultaneously
|
|
174
|
+
await Promise.all([
|
|
175
|
+
page.waitForURL((url) => !url.pathname.includes('/my-form'), { timeout: 30000 }),
|
|
176
|
+
ldf.block('submit_btn').do.click(),
|
|
177
|
+
]);
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Why not `ldf.request().expect.toFinish()`?** When the page navigates away, the page context is destroyed. Any pending request assertions will fail because the Lowdefy runtime that tracks requests no longer exists. Use `page.waitForURL()` instead to assert that the navigation happened.
|
|
182
|
+
|
|
139
183
|
## Playwright Configuration
|
|
140
184
|
|
|
141
185
|
Edit `playwright.config.js` to customize test behavior.
|
|
@@ -144,7 +188,7 @@ Edit `playwright.config.js` to customize test behavior.
|
|
|
144
188
|
|
|
145
189
|
| Option | Default | Description |
|
|
146
190
|
|--------|---------|-------------|
|
|
147
|
-
| `appDir` | `'
|
|
191
|
+
| `appDir` | `'./'` | Path to your Lowdefy app root (relative to cwd) |
|
|
148
192
|
| `buildDir` | `'.lowdefy/server/build'` | Build output directory (relative to appDir) |
|
|
149
193
|
| `mocksFile` | `'e2e/mocks.yaml'` | Static mocks file path (relative to appDir) |
|
|
150
194
|
| `port` | `3000` | Port for the test server |
|
|
@@ -160,7 +204,7 @@ Edit `playwright.config.js` to customize test behavior.
|
|
|
160
204
|
import { createConfig } from '@lowdefy/e2e-utils/config';
|
|
161
205
|
|
|
162
206
|
export default createConfig({
|
|
163
|
-
appDir: '
|
|
207
|
+
appDir: './',
|
|
164
208
|
port: 3001, // Use different port
|
|
165
209
|
timeout: 300000, // 5 min timeout for slow builds
|
|
166
210
|
screenshot: 'on', // Always capture screenshots
|
|
@@ -177,8 +221,8 @@ import { createMultiAppConfig } from '@lowdefy/e2e-utils/config';
|
|
|
177
221
|
|
|
178
222
|
export default createMultiAppConfig({
|
|
179
223
|
apps: [
|
|
180
|
-
{ name: 'admin', appDir: '
|
|
181
|
-
{ name: 'customer', appDir: '
|
|
224
|
+
{ name: 'admin', appDir: './apps/admin', port: 3001 },
|
|
225
|
+
{ name: 'customer', appDir: './apps/customer', port: 3002 },
|
|
182
226
|
],
|
|
183
227
|
testDir: '.',
|
|
184
228
|
timeout: 180000,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { test, expect } from '@lowdefy/e2e-utils/fixtures';
|
|
2
2
|
|
|
3
|
-
test('
|
|
4
|
-
await ldf.goto('/');
|
|
5
|
-
|
|
6
|
-
// Replace with your own assertions
|
|
7
|
-
await expect(ldf.page).toHaveTitle(/./);
|
|
3
|
+
test('app is running', async ({ ldf }) => {
|
|
4
|
+
const response = await ldf.page.goto('/api/auth/session');
|
|
5
|
+
expect(response.status()).toBe(200);
|
|
8
6
|
});
|
|
9
7
|
|
|
10
8
|
// Example: Testing a form
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { test as ldfTest, expect } from '@lowdefy/e2e-utils/fixtures';
|
|
2
|
-
import {
|
|
2
|
+
import { mdbFixtures } from '@lowdefy/community-plugin-e2e-mdb/fixtures';
|
|
3
|
+
import { mergeTests } from '@playwright/test';
|
|
3
4
|
|
|
4
|
-
export const test = ldfTest
|
|
5
|
+
export const test = mergeTests(ldfTest, mdbFixtures);
|
|
5
6
|
export { expect };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createConfig } from '@lowdefy/e2e-utils/config';
|
|
2
2
|
|
|
3
3
|
export default createConfig({
|
|
4
|
-
appDir: '
|
|
4
|
+
appDir: './', // Resolved relative to cwd (where pnpm e2e runs)
|
|
5
5
|
testDir: '.', // Tests are in same dir as config
|
|
6
6
|
port: 3000,
|
|
7
7
|
// If your app needs environment variables (e.g., via infisical, dotenv-vault, aws-vault):
|
|
@@ -26,28 +26,16 @@ function generateManifest({ buildDir = '.lowdefy' }) {
|
|
|
26
26
|
pages: {}
|
|
27
27
|
};
|
|
28
28
|
if (fs.existsSync(pagesDir)) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (entry.name === 'requests') continue;
|
|
38
|
-
findPageFiles(path.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
39
|
-
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
40
|
-
const pageId = prefix ? `${prefix}/${entry.name.replace(/\.json$/, '')}` : entry.name.replace(/\.json$/, '');
|
|
41
|
-
const pageConfigPath = path.join(dir, entry.name);
|
|
42
|
-
const pageConfig = JSON.parse(fs.readFileSync(pageConfigPath, 'utf-8'));
|
|
43
|
-
manifest.pages[pageId] = extractBlockMap({
|
|
44
|
-
pageConfig,
|
|
45
|
-
typesBlocks: types.blocks ?? {}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
29
|
+
for (const pageId of fs.readdirSync(pagesDir)){
|
|
30
|
+
const pageConfigPath = path.join(pagesDir, pageId, `${pageId}.json`);
|
|
31
|
+
if (fs.existsSync(pageConfigPath)) {
|
|
32
|
+
const pageConfig = JSON.parse(fs.readFileSync(pageConfigPath, 'utf-8'));
|
|
33
|
+
manifest.pages[pageId] = extractBlockMap({
|
|
34
|
+
pageConfig,
|
|
35
|
+
typesBlocks: types.blocks ?? {}
|
|
36
|
+
});
|
|
48
37
|
}
|
|
49
38
|
}
|
|
50
|
-
findPageFiles(pagesDir, '');
|
|
51
39
|
}
|
|
52
40
|
const manifestPath = path.join(buildDir, 'e2e-manifest.json');
|
|
53
41
|
const tempPath = path.join(buildDir, `e2e-manifest.${process.pid}.tmp`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/e2e-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Lowdefy E2E Testing Utilities for Playwright",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dist/*"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@lowdefy/helpers": "
|
|
39
|
+
"@lowdefy/helpers": "4.7.0",
|
|
40
40
|
"js-yaml": "4.1.0",
|
|
41
41
|
"prompts": "2.4.2"
|
|
42
42
|
},
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@swc/cli": "0.1.63",
|
|
48
48
|
"@swc/core": "1.3.99",
|
|
49
|
-
"copyfiles": "2.4.1"
|
|
49
|
+
"copyfiles": "2.4.1",
|
|
50
|
+
"jest": "28.1.3"
|
|
50
51
|
},
|
|
51
52
|
"publishConfig": {
|
|
52
53
|
"access": "public"
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"scripts": {
|
|
55
56
|
"build": "swc src --out-dir dist --config-file ../../../.swcrc --delete-dir-on-start && pnpm copyfiles",
|
|
56
57
|
"clean": "rm -rf dist",
|
|
57
|
-
"copyfiles": "copyfiles -u 1 \"./src/init/templates/*\" dist"
|
|
58
|
+
"copyfiles": "copyfiles -u 1 \"./src/init/templates/*\" dist",
|
|
59
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
58
60
|
}
|
|
59
61
|
}
|