@lowdefy/e2e-utils 4.6.0 → 4.7.1
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/api.js +52 -0
- package/dist/core/escapeId.js +18 -0
- package/dist/core/locators.js +3 -2
- package/dist/core/navigation.js +9 -2
- package/dist/index.js +2 -0
- package/dist/init/generateFiles.js +8 -0
- package/dist/init/index.js +39 -46
- package/dist/init/templates/README.md.template +51 -7
- package/dist/init/templates/env.e2e.template +1 -1
- 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/mocking/createMockManager.js +14 -0
- package/dist/proxy/createBlockHelper.js +7 -0
- package/dist/proxy/createPageManager.js +28 -0
- package/package.json +6 -4
- package/dist/init/installDeps.js +0 -62
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
|
package/dist/core/api.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
*/ import { expect } from '@playwright/test';
|
|
16
|
+
async function getApiState(page, endpointId) {
|
|
17
|
+
return page.evaluate((id)=>{
|
|
18
|
+
const lowdefy = window.lowdefy;
|
|
19
|
+
return lowdefy?.apiResponses?.[id]?.[0];
|
|
20
|
+
}, endpointId);
|
|
21
|
+
}
|
|
22
|
+
async function getApiResponse(page, { endpointId }) {
|
|
23
|
+
const state = await getApiState(page, endpointId);
|
|
24
|
+
return state?.response;
|
|
25
|
+
}
|
|
26
|
+
async function expectApi(page, { endpointId, loading, response, payload, timeout = 30000 }) {
|
|
27
|
+
if (loading !== undefined) {
|
|
28
|
+
await expect.poll(async ()=>{
|
|
29
|
+
const state = await getApiState(page, endpointId);
|
|
30
|
+
return state?.loading;
|
|
31
|
+
}, {
|
|
32
|
+
timeout
|
|
33
|
+
}).toBe(loading);
|
|
34
|
+
}
|
|
35
|
+
if (response !== undefined) {
|
|
36
|
+
await expect.poll(async ()=>{
|
|
37
|
+
const state = await getApiState(page, endpointId);
|
|
38
|
+
return state?.response;
|
|
39
|
+
}, {
|
|
40
|
+
timeout
|
|
41
|
+
}).toEqual(expect.objectContaining(response));
|
|
42
|
+
}
|
|
43
|
+
if (payload !== undefined) {
|
|
44
|
+
await expect.poll(async ()=>{
|
|
45
|
+
const state = await getApiState(page, endpointId);
|
|
46
|
+
return state?.payload;
|
|
47
|
+
}, {
|
|
48
|
+
timeout
|
|
49
|
+
}).toEqual(expect.objectContaining(payload));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export { getApiState, getApiResponse, expectApi };
|
|
@@ -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/core/navigation.js
CHANGED
|
@@ -24,14 +24,21 @@ async function waitForReady(page) {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
async function goto(page, path) {
|
|
27
|
-
|
|
27
|
+
// domcontentloaded is sufficient — waitForReady gates on the Lowdefy client
|
|
28
|
+
// context, which is a stronger readiness signal than the browser 'load' event.
|
|
29
|
+
// Using 'load' can hang when pages have WebSocket connections or slow resources.
|
|
30
|
+
await page.goto(path, {
|
|
31
|
+
waitUntil: 'domcontentloaded'
|
|
32
|
+
});
|
|
28
33
|
await waitForReady(page);
|
|
29
34
|
}
|
|
30
35
|
async function expectNavigation(page, urlPattern) {
|
|
31
36
|
await expect(page).toHaveURL(urlPattern);
|
|
32
37
|
}
|
|
33
38
|
async function waitForPage(page, path) {
|
|
34
|
-
await page.waitForURL(path
|
|
39
|
+
await page.waitForURL(path, {
|
|
40
|
+
waitUntil: 'domcontentloaded'
|
|
41
|
+
});
|
|
35
42
|
await waitForReady(page);
|
|
36
43
|
}
|
|
37
44
|
export { goto, waitForReady, expectNavigation, waitForPage };
|
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
|
@@ -13,12 +13,22 @@
|
|
|
13
13
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
See the License for the specific language governing permissions and
|
|
15
15
|
limitations under the License.
|
|
16
|
-
*/ /* eslint-disable no-console */ import
|
|
16
|
+
*/ /* eslint-disable no-console */ import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
17
18
|
import prompts from 'prompts';
|
|
18
19
|
import detectApps from './detectApps.js';
|
|
19
20
|
import generateFiles from './generateFiles.js';
|
|
20
|
-
import installDeps, { detectPackageManager } from './installDeps.js';
|
|
21
21
|
import updateGitignore from './updateGitignore.js';
|
|
22
|
+
function detectPackageManager(cwd) {
|
|
23
|
+
let dir = cwd;
|
|
24
|
+
while(dir !== path.dirname(dir)){
|
|
25
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
26
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn';
|
|
27
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json'))) return 'npm';
|
|
28
|
+
dir = path.dirname(dir);
|
|
29
|
+
}
|
|
30
|
+
return 'npm';
|
|
31
|
+
}
|
|
22
32
|
async function init() {
|
|
23
33
|
const cwd = process.cwd();
|
|
24
34
|
console.log('Setting up Lowdefy e2e testing...\n');
|
|
@@ -106,57 +116,40 @@ async function init() {
|
|
|
106
116
|
}
|
|
107
117
|
// Step 6: Update .gitignore
|
|
108
118
|
updateGitignore(cwd);
|
|
109
|
-
// Step 7:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
console.log('\nSetup cancelled.');
|
|
119
|
-
process.exit(0);
|
|
119
|
+
// Step 7: Print completion summary and next steps
|
|
120
|
+
console.log('\n✓ E2E testing setup complete!\n');
|
|
121
|
+
console.log('Next steps:\n');
|
|
122
|
+
let step = 1;
|
|
123
|
+
// Install instructions
|
|
124
|
+
const packageManager = detectPackageManager(cwd);
|
|
125
|
+
console.log(` ${step}. Install dependencies:`);
|
|
126
|
+
if (selectedApps.length === 1) {
|
|
127
|
+
console.log(` cd ${selectedApps[0].path}`);
|
|
120
128
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
for (const app of selectedApps){
|
|
132
|
-
const packageManager = detectPackageManager(path.join(cwd, app.path));
|
|
133
|
-
console.log(`\nTo install dependencies manually in ${app.path}:`);
|
|
134
|
-
console.log(` cd ${app.path}`);
|
|
135
|
-
console.log(` ${packageManager} install`);
|
|
136
|
-
console.log(' npx playwright install chromium');
|
|
137
|
-
}
|
|
129
|
+
console.log(` ${packageManager} install`);
|
|
130
|
+
console.log(' npx playwright install chromium');
|
|
131
|
+
step += 1;
|
|
132
|
+
// MongoDB setup
|
|
133
|
+
if (useMongoDB) {
|
|
134
|
+
console.log(`\n ${step}. Configure MongoDB testing:`);
|
|
135
|
+
console.log(' Copy .env.e2e to .env.e2e.local');
|
|
136
|
+
console.log(' Set LOWDEFY_E2E_MONGODB_URI to your test database');
|
|
137
|
+
console.log(' See mongodb.spec.js for example tests');
|
|
138
|
+
step += 1;
|
|
138
139
|
}
|
|
139
|
-
//
|
|
140
|
-
console.log(
|
|
140
|
+
// Run tests
|
|
141
|
+
console.log(`\n ${step}. Run tests:`);
|
|
141
142
|
if (selectedApps.length === 1) {
|
|
142
|
-
|
|
143
|
-
console.log(
|
|
144
|
-
console.log(
|
|
145
|
-
console.log('
|
|
146
|
-
console.log('
|
|
143
|
+
console.log(` cd ${selectedApps[0].path}`);
|
|
144
|
+
console.log(' pnpm e2e # Run all tests');
|
|
145
|
+
console.log(' pnpm e2e:headed # Run with visible browser');
|
|
146
|
+
console.log(' pnpm e2e:ui # Playwright UI mode');
|
|
147
|
+
console.log(' pnpm e2e:server # Start server for reuse');
|
|
147
148
|
} else {
|
|
148
|
-
console.log('Run tests from each app directory:');
|
|
149
149
|
for (const app of selectedApps){
|
|
150
|
-
console.log(
|
|
151
|
-
console.log(` cd ${app.path} && pnpm e2e`);
|
|
150
|
+
console.log(` cd ${app.path} && pnpm e2e`);
|
|
152
151
|
}
|
|
153
152
|
}
|
|
154
|
-
if (useMongoDB) {
|
|
155
|
-
console.log('\nMongoDB testing setup:');
|
|
156
|
-
console.log(' 1. Copy .env.e2e to .env.e2e.local');
|
|
157
|
-
console.log(' 2. Set MDB_E2E_URI to your test database');
|
|
158
|
-
console.log(' 3. See mongodb.spec.js for example tests');
|
|
159
|
-
}
|
|
160
153
|
}
|
|
161
154
|
init().catch((err)=>{
|
|
162
155
|
console.error('Error:', err.message);
|
|
@@ -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,
|
|
@@ -280,7 +324,7 @@ Copy `.env.e2e` to `.env.e2e.local` and set your values:
|
|
|
280
324
|
cp e2e/.env.e2e e2e/.env.e2e.local
|
|
281
325
|
```
|
|
282
326
|
|
|
283
|
-
For MongoDB testing, set `
|
|
327
|
+
For MongoDB testing, set `LOWDEFY_E2E_MONGODB_URI` to a dedicated test database (data will be cleared during tests). If using `@lowdefy/community-plugin-e2e-mdb` with `configureMdb()`, the URI is set automatically.
|
|
284
328
|
|
|
285
329
|
## More Information
|
|
286
330
|
|
|
@@ -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):
|
|
@@ -82,11 +82,16 @@ function createMockManager({ page }) {
|
|
|
82
82
|
async function mockApi(apiId, { response, error, method }) {
|
|
83
83
|
const key = `api:${apiId}`;
|
|
84
84
|
const pattern = `**/api/endpoints/${apiId}`;
|
|
85
|
+
const captureKey = `api:${apiId}`;
|
|
85
86
|
const handler = async (route)=>{
|
|
86
87
|
if (method && route.request().method() !== method.toUpperCase()) {
|
|
87
88
|
await route.continue();
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
91
|
+
capturedRequests.set(captureKey, {
|
|
92
|
+
payload: capturePayload(route),
|
|
93
|
+
timestamp: Date.now()
|
|
94
|
+
});
|
|
90
95
|
await fulfillRoute(route, {
|
|
91
96
|
response,
|
|
92
97
|
error
|
|
@@ -118,11 +123,16 @@ function createMockManager({ page }) {
|
|
|
118
123
|
for (const config of api){
|
|
119
124
|
const pattern = `**/api/endpoints/${config.endpointId}`;
|
|
120
125
|
const key = `static:api:${config.endpointId}`;
|
|
126
|
+
const captureKey = `api:${config.endpointId}`;
|
|
121
127
|
const handler = async (route)=>{
|
|
122
128
|
if (config.method && route.request().method() !== config.method.toUpperCase()) {
|
|
123
129
|
await route.continue();
|
|
124
130
|
return;
|
|
125
131
|
}
|
|
132
|
+
capturedRequests.set(captureKey, {
|
|
133
|
+
payload: capturePayload(route),
|
|
134
|
+
timestamp: Date.now()
|
|
135
|
+
});
|
|
126
136
|
await fulfillRoute(route, {
|
|
127
137
|
response: config.response,
|
|
128
138
|
error: config.error
|
|
@@ -141,6 +151,9 @@ function createMockManager({ page }) {
|
|
|
141
151
|
const captureKey = pageId ? `${pageId}:${requestId}` : requestId;
|
|
142
152
|
return capturedRequests.get(captureKey) ?? null;
|
|
143
153
|
}
|
|
154
|
+
function getCapturedApi(endpointId) {
|
|
155
|
+
return capturedRequests.get(`api:${endpointId}`) ?? null;
|
|
156
|
+
}
|
|
144
157
|
function clearCapturedRequests() {
|
|
145
158
|
capturedRequests.clear();
|
|
146
159
|
}
|
|
@@ -150,6 +163,7 @@ function createMockManager({ page }) {
|
|
|
150
163
|
applyStaticMocks,
|
|
151
164
|
cleanup,
|
|
152
165
|
getCapturedRequest,
|
|
166
|
+
getCapturedApi,
|
|
153
167
|
clearCapturedRequests
|
|
154
168
|
};
|
|
155
169
|
}
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { expect } from '@playwright/test';
|
|
16
16
|
import { expectValidationError, expectValidationWarning, expectValidationSuccess } from '../core/validation.js';
|
|
17
|
+
// Each block's e2e helper defines a `locator` function that targets the block element on the page.
|
|
18
|
+
// Two locator patterns exist:
|
|
19
|
+
// - `#${escapeId(blockId)}` — for blocks that render `id={blockId}` on their root DOM element
|
|
20
|
+
// (e.g. AgGrid variants, ProgressBar, Markdown variants, QRScanner).
|
|
21
|
+
// - `#bl-${escapeId(blockId)}` — for blocks whose root element does not carry the blockId;
|
|
22
|
+
// targets the Lowdefy layout wrapper div instead
|
|
23
|
+
// (e.g. Skeleton variants, Spinner, EChart, DocSearch, ColorSelector, GoogleMaps variants).
|
|
17
24
|
function createBlockHelper({ locator, do: doMethods, get: getMethods, expect: expectOverrides }) {
|
|
18
25
|
const commonExpect = {
|
|
19
26
|
visible: (page, blockId)=>expect(locator(page, blockId)).toBeVisible(),
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { waitForReady, waitForPage } from '../core/navigation.js';
|
|
17
17
|
import { getState, getBlockState, setState, expectState } from '../core/state.js';
|
|
18
18
|
import { getRequestState, getRequestResponse, expectRequest } from '../core/requests.js';
|
|
19
|
+
import { getApiState, getApiResponse, expectApi } from '../core/api.js';
|
|
19
20
|
import { getValidation } from '../core/validation.js';
|
|
20
21
|
import { expectUrl, expectUrlQuery, setUrlQuery } from '../core/url.js';
|
|
21
22
|
import { setUserCookie, clearUserCookie } from '../core/userCookie.js';
|
|
@@ -115,6 +116,32 @@ function createPageManager({ page, manifest, helperRegistry, mockManager }) {
|
|
|
115
116
|
state: ()=>getRequestState(page, requestId)
|
|
116
117
|
};
|
|
117
118
|
},
|
|
119
|
+
// API endpoint locator - ldf.api('id').expect.*/response()/state()
|
|
120
|
+
api (endpointId) {
|
|
121
|
+
return {
|
|
122
|
+
expect: {
|
|
123
|
+
toFinish: (opts)=>expectApi(page, {
|
|
124
|
+
endpointId,
|
|
125
|
+
loading: false,
|
|
126
|
+
...opts
|
|
127
|
+
}),
|
|
128
|
+
toHaveResponse: (response, opts)=>expectApi(page, {
|
|
129
|
+
endpointId,
|
|
130
|
+
response,
|
|
131
|
+
...opts
|
|
132
|
+
}),
|
|
133
|
+
toHavePayload: (payload, opts)=>expectApi(page, {
|
|
134
|
+
endpointId,
|
|
135
|
+
payload,
|
|
136
|
+
...opts
|
|
137
|
+
})
|
|
138
|
+
},
|
|
139
|
+
response: ()=>getApiResponse(page, {
|
|
140
|
+
endpointId
|
|
141
|
+
}),
|
|
142
|
+
state: ()=>getApiState(page, endpointId)
|
|
143
|
+
};
|
|
144
|
+
},
|
|
118
145
|
// State locator - ldf.state('key').do.*/expect.*/value() or ldf.state().value()
|
|
119
146
|
state (key) {
|
|
120
147
|
if (type.isNone(key)) {
|
|
@@ -185,6 +212,7 @@ function createPageManager({ page, manifest, helperRegistry, mockManager }) {
|
|
|
185
212
|
},
|
|
186
213
|
api: (apiId, options)=>mockManager?.mockApi(apiId, options),
|
|
187
214
|
getCapturedRequest: (requestId, opts)=>mockManager?.getCapturedRequest(requestId, opts),
|
|
215
|
+
getCapturedApi: (endpointId)=>mockManager?.getCapturedApi(endpointId),
|
|
188
216
|
clearCapturedRequests: ()=>mockManager?.clearCapturedRequests()
|
|
189
217
|
}
|
|
190
218
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/e2e-utils",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.1",
|
|
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": "4.
|
|
39
|
+
"@lowdefy/helpers": "4.7.1",
|
|
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
|
}
|
package/dist/init/installDeps.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
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
|
-
*/ /* eslint-disable no-console */ import fs from 'fs';
|
|
16
|
-
import path from 'path';
|
|
17
|
-
import { execSync } from 'child_process';
|
|
18
|
-
function detectPackageManager(cwd) {
|
|
19
|
-
// Walk up from cwd to find the lock file
|
|
20
|
-
let dir = cwd;
|
|
21
|
-
while(dir !== path.dirname(dir)){
|
|
22
|
-
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
23
|
-
if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn';
|
|
24
|
-
if (fs.existsSync(path.join(dir, 'package-lock.json'))) return 'npm';
|
|
25
|
-
dir = path.dirname(dir);
|
|
26
|
-
}
|
|
27
|
-
return 'npm';
|
|
28
|
-
}
|
|
29
|
-
function installDeps({ appDir }) {
|
|
30
|
-
const packageManager = detectPackageManager(appDir);
|
|
31
|
-
const installCmd = `${packageManager} install`;
|
|
32
|
-
console.log(`\nInstalling dependencies in ${appDir}...`);
|
|
33
|
-
console.log(` $ ${installCmd}\n`);
|
|
34
|
-
try {
|
|
35
|
-
execSync(installCmd, {
|
|
36
|
-
cwd: appDir,
|
|
37
|
-
stdio: 'inherit'
|
|
38
|
-
});
|
|
39
|
-
console.log('\n✓ Dependencies installed successfully');
|
|
40
|
-
} catch {
|
|
41
|
-
console.error('\n✗ Failed to install dependencies automatically');
|
|
42
|
-
console.log(`\nInstall manually from ${appDir}:`);
|
|
43
|
-
console.log(` ${installCmd}`);
|
|
44
|
-
console.log(' npx playwright install chromium');
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
console.log('\nInstalling Playwright browsers...');
|
|
48
|
-
console.log(' $ npx playwright install chromium\n');
|
|
49
|
-
try {
|
|
50
|
-
execSync('npx playwright install chromium', {
|
|
51
|
-
cwd: appDir,
|
|
52
|
-
stdio: 'inherit'
|
|
53
|
-
});
|
|
54
|
-
console.log('\n✓ Playwright browsers installed');
|
|
55
|
-
} catch {
|
|
56
|
-
console.error('\n✗ Failed to install Playwright browsers');
|
|
57
|
-
console.error(' Run manually: npx playwright install chromium');
|
|
58
|
-
}
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
export { detectPackageManager };
|
|
62
|
-
export default installDeps;
|