@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 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,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 };
@@ -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
- */ function getBlock(page, blockId) {
16
- return page.locator(`#bl-${blockId}`);
15
+ */ import { escapeId } from './escapeId.js';
16
+ function getBlock(page, blockId) {
17
+ return page.locator(`#bl-${escapeId(blockId)}`);
17
18
  }
18
19
  export { getBlock };
@@ -24,14 +24,21 @@ async function waitForReady(page) {
24
24
  });
25
25
  }
26
26
  async function goto(page, path) {
27
- await page.goto(path);
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 || {};
@@ -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 path from 'path';
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: Ask about dependency installation
110
- const installResponse = await prompts({
111
- type: 'confirm',
112
- name: 'install',
113
- message: 'Would you like to run install now?',
114
- initial: true
115
- });
116
- // Handle Ctrl+C
117
- if (installResponse.install === undefined) {
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
- if (installResponse.install) {
122
- // Step 8: Install dependencies for each selected app
123
- for (const app of selectedApps){
124
- const appDir = path.join(cwd, app.path);
125
- installDeps({
126
- appDir
127
- });
128
- }
129
- } else {
130
- // Print manual install instructions per app
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
- // Step 9: Print completion summary
140
- console.log('\n E2E testing setup complete!\n');
140
+ // Run tests
141
+ console.log(`\n ${step}. Run tests:`);
141
142
  if (selectedApps.length === 1) {
142
- const app = selectedApps[0];
143
- console.log(`Run tests from ${app.path}:`);
144
- console.log(` cd ${app.path}`);
145
- console.log(' pnpm e2e');
146
- console.log(' pnpm e2e:ui # for UI mode');
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(`\n ${app.name}:`);
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 # Run all tests
9
- pnpm e2e:ui # Run in UI mode (interactive)
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` | `'../'` | Path to your Lowdefy app root (relative to config file) |
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: '../apps/admin', port: 3001 },
181
- { name: 'customer', appDir: '../apps/customer', port: 3002 },
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 `MDB_E2E_URI` to a dedicated test database (data will be cleared during tests).
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,4 +1,4 @@
1
1
  # MongoDB E2E Testing Configuration
2
2
  # Copy this file to .env.e2e.local and fill in your test database URI
3
3
 
4
- MDB_E2E_URI=mongodb://localhost:27017/your-test-db
4
+ LOWDEFY_E2E_MONGODB_URI=mongodb://localhost:27017/your-test-db
@@ -1,10 +1,8 @@
1
1
  import { test, expect } from '@lowdefy/e2e-utils/fixtures';
2
2
 
3
- test('homepage loads', async ({ ldf }) => {
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 { mdbFixture } from '@lowdefy/community-plugin-e2e-mdb';
2
+ import { mdbFixtures } from '@lowdefy/community-plugin-e2e-mdb/fixtures';
3
+ import { mergeTests } from '@playwright/test';
3
4
 
4
- export const test = ldfTest.extend(mdbFixture);
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: '../', // Go up from e2e/ to app root
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.6.0",
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.6.0",
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
  }
@@ -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;