@lowdefy/e2e-utils 5.6.0 → 6.0.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/README.md +16 -0
- package/dist/assertions/api.js +43 -0
- package/dist/assertions/index.js +20 -0
- package/dist/assertions/navigation.js +19 -0
- package/dist/assertions/requests.js +45 -0
- package/dist/assertions/state.js +26 -0
- package/dist/assertions/url.js +32 -0
- package/dist/assertions/validation.js +64 -0
- package/dist/config.js +1 -4
- package/dist/core/api.js +2 -29
- package/dist/core/index.js +7 -6
- package/dist/core/keyboard.js +23 -0
- package/dist/core/navigation.js +2 -6
- package/dist/core/requests.js +2 -31
- package/dist/core/state.js +7 -17
- package/dist/core/url.js +69 -26
- package/dist/core/validation.js +2 -50
- package/dist/fixtures/index.js +16 -1
- package/dist/index.js +24 -11
- package/dist/proxy/createBlockHelper.js +1 -1
- package/dist/proxy/createPageManager.js +25 -15
- package/dist/runtime.js +33 -0
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -151,6 +151,8 @@ await ldf.url().expect.toMatch(/\/tickets\/\d+/);
|
|
|
151
151
|
|
|
152
152
|
// Query params
|
|
153
153
|
await ldf.urlQuery('filter').expect.toBe('active');
|
|
154
|
+
// do.set navigates through the app router, so a Dynamic page re-resolves its
|
|
155
|
+
// content for the new query before this resolves.
|
|
154
156
|
await ldf.urlQuery('page').do.set('2');
|
|
155
157
|
|
|
156
158
|
// Get raw values
|
|
@@ -205,6 +207,20 @@ const blockState = await ldf.block('name_input').state();
|
|
|
205
207
|
const validation = await ldf.block('email_input').validation();
|
|
206
208
|
```
|
|
207
209
|
|
|
210
|
+
### Keyboard Shortcuts
|
|
211
|
+
|
|
212
|
+
A `mod` shortcut resolves to Cmd or Ctrl depending on the platform the **browser** reports, which
|
|
213
|
+
Playwright emulates per project — a Desktop Chrome project reports Windows even on macOS. Derive the
|
|
214
|
+
key from the page rather than from `process.platform`, or the test will press a key the app is not
|
|
215
|
+
listening for:
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
import { getShortcutModifier } from '@lowdefy/e2e-utils';
|
|
219
|
+
|
|
220
|
+
const mod = await getShortcutModifier(ldf.page);
|
|
221
|
+
await ldf.page.keyboard.press(`${mod}+k`);
|
|
222
|
+
```
|
|
223
|
+
|
|
208
224
|
## Complete Example
|
|
209
225
|
|
|
210
226
|
```javascript
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
import { getApiState } from '../core/api.js';
|
|
17
|
+
async function expectApi(page, { endpointId, loading, response, payload, timeout = 30000 }) {
|
|
18
|
+
if (loading !== undefined) {
|
|
19
|
+
await expect.poll(async ()=>{
|
|
20
|
+
const state = await getApiState(page, endpointId);
|
|
21
|
+
return state?.loading;
|
|
22
|
+
}, {
|
|
23
|
+
timeout
|
|
24
|
+
}).toBe(loading);
|
|
25
|
+
}
|
|
26
|
+
if (response !== undefined) {
|
|
27
|
+
await expect.poll(async ()=>{
|
|
28
|
+
const state = await getApiState(page, endpointId);
|
|
29
|
+
return state?.response;
|
|
30
|
+
}, {
|
|
31
|
+
timeout
|
|
32
|
+
}).toEqual(expect.objectContaining(response));
|
|
33
|
+
}
|
|
34
|
+
if (payload !== undefined) {
|
|
35
|
+
await expect.poll(async ()=>{
|
|
36
|
+
const state = await getApiState(page, endpointId);
|
|
37
|
+
return state?.payload;
|
|
38
|
+
}, {
|
|
39
|
+
timeout
|
|
40
|
+
}).toEqual(expect.objectContaining(payload));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export { expectApi };
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
*/ export { expectState } from './state.js';
|
|
16
|
+
export { expectRequest } from './requests.js';
|
|
17
|
+
export { expectApi } from './api.js';
|
|
18
|
+
export { expectNavigation } from './navigation.js';
|
|
19
|
+
export { expectUrl, expectUrlQuery } from './url.js';
|
|
20
|
+
export { expectValidationStatus, expectValidationError, expectValidationWarning, expectValidationSuccess } from './validation.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
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 expectNavigation(page, urlPattern) {
|
|
17
|
+
await expect(page).toHaveURL(urlPattern);
|
|
18
|
+
}
|
|
19
|
+
export { expectNavigation };
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
import { getRequestState } from '../core/requests.js';
|
|
17
|
+
async function expectRequest(page, { requestId, loading, response, payload, timeout = 30000 }) {
|
|
18
|
+
if (loading !== undefined) {
|
|
19
|
+
await expect.poll(async ()=>{
|
|
20
|
+
const state = await getRequestState(page, requestId);
|
|
21
|
+
return state?.loading;
|
|
22
|
+
}, {
|
|
23
|
+
timeout
|
|
24
|
+
}).toBe(loading);
|
|
25
|
+
}
|
|
26
|
+
if (response !== undefined) {
|
|
27
|
+
await expect.poll(async ()=>{
|
|
28
|
+
const state = await getRequestState(page, requestId);
|
|
29
|
+
return state?.response;
|
|
30
|
+
}, {
|
|
31
|
+
timeout
|
|
32
|
+
}).toEqual(expect.objectContaining(response));
|
|
33
|
+
}
|
|
34
|
+
// Payload is stored in the request state by the Lowdefy engine.
|
|
35
|
+
// It's the evaluated payload from the request config (client-side).
|
|
36
|
+
if (payload !== undefined) {
|
|
37
|
+
await expect.poll(async ()=>{
|
|
38
|
+
const state = await getRequestState(page, requestId);
|
|
39
|
+
return state?.payload;
|
|
40
|
+
}, {
|
|
41
|
+
timeout
|
|
42
|
+
}).toEqual(expect.objectContaining(payload));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export { expectRequest };
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
import { get } from '@lowdefy/helpers';
|
|
17
|
+
import { getState } from '../core/state.js';
|
|
18
|
+
async function expectState(page, { key, value, timeout = 5000 }) {
|
|
19
|
+
await expect.poll(async ()=>{
|
|
20
|
+
const state = await getState(page);
|
|
21
|
+
return get(state, key);
|
|
22
|
+
}, {
|
|
23
|
+
timeout
|
|
24
|
+
}).toEqual(value);
|
|
25
|
+
}
|
|
26
|
+
export { expectState };
|
|
@@ -0,0 +1,32 @@
|
|
|
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 expectUrl(page, { url, timeout = 5000 }) {
|
|
17
|
+
if (!url) {
|
|
18
|
+
throw new Error('expectUrl requires a "url" parameter (string or RegExp).');
|
|
19
|
+
}
|
|
20
|
+
await expect(page).toHaveURL(url, {
|
|
21
|
+
timeout
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function expectUrlQuery(page, { key, value, timeout = 5000 }) {
|
|
25
|
+
await expect.poll(async ()=>{
|
|
26
|
+
const url = new URL(page.url());
|
|
27
|
+
return url.searchParams.get(key);
|
|
28
|
+
}, {
|
|
29
|
+
timeout
|
|
30
|
+
}).toBe(value);
|
|
31
|
+
}
|
|
32
|
+
export { expectUrl, expectUrlQuery };
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
import { getValidation } from '../core/validation.js';
|
|
17
|
+
async function expectValidationStatus(page, { blockId, status, timeout = 5000 }) {
|
|
18
|
+
await expect.poll(async ()=>{
|
|
19
|
+
const validation = await getValidation(page, blockId);
|
|
20
|
+
return validation?.status;
|
|
21
|
+
}, {
|
|
22
|
+
timeout
|
|
23
|
+
}).toBe(status);
|
|
24
|
+
}
|
|
25
|
+
async function expectValidationError(page, { blockId, message, timeout = 5000 }) {
|
|
26
|
+
await expect.poll(async ()=>{
|
|
27
|
+
const validation = await getValidation(page, blockId);
|
|
28
|
+
if (validation?.status !== 'error') return {
|
|
29
|
+
status: validation?.status
|
|
30
|
+
};
|
|
31
|
+
if (message && !validation?.errors?.includes(message)) {
|
|
32
|
+
return {
|
|
33
|
+
errors: validation?.errors
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}, {
|
|
38
|
+
timeout
|
|
39
|
+
}).toBe(true);
|
|
40
|
+
}
|
|
41
|
+
async function expectValidationWarning(page, { blockId, message, timeout = 5000 }) {
|
|
42
|
+
await expect.poll(async ()=>{
|
|
43
|
+
const validation = await getValidation(page, blockId);
|
|
44
|
+
if (validation?.status !== 'warning') return {
|
|
45
|
+
status: validation?.status
|
|
46
|
+
};
|
|
47
|
+
if (message && !validation?.warnings?.includes(message)) {
|
|
48
|
+
return {
|
|
49
|
+
warnings: validation?.warnings
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}, {
|
|
54
|
+
timeout
|
|
55
|
+
}).toBe(true);
|
|
56
|
+
}
|
|
57
|
+
async function expectValidationSuccess(page, { blockId, timeout = 5000 }) {
|
|
58
|
+
await expectValidationStatus(page, {
|
|
59
|
+
blockId,
|
|
60
|
+
status: 'success',
|
|
61
|
+
timeout
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export { expectValidationStatus, expectValidationError, expectValidationWarning, expectValidationSuccess };
|
package/dist/config.js
CHANGED
|
@@ -94,10 +94,7 @@ function createMultiAppConfig({ apps = [], commandPrefix = '', testDir = 'e2e',
|
|
|
94
94
|
url: `http://localhost:${app.port}/api/auth/session`,
|
|
95
95
|
reuseExistingServer: true,
|
|
96
96
|
timeout,
|
|
97
|
-
cwd: app.appDir
|
|
98
|
-
env: {
|
|
99
|
-
NEXT_PUBLIC_LOWDEFY_E2E: 'true'
|
|
100
|
-
}
|
|
97
|
+
cwd: app.appDir
|
|
101
98
|
};
|
|
102
99
|
});
|
|
103
100
|
return defineConfig({
|
package/dist/core/api.js
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
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
|
-
async function getApiState(page, endpointId) {
|
|
15
|
+
*/ async function getApiState(page, endpointId) {
|
|
17
16
|
return page.evaluate((id)=>{
|
|
18
17
|
const lowdefy = window.lowdefy;
|
|
19
18
|
return lowdefy?.apiResponses?.[id]?.[0];
|
|
@@ -23,30 +22,4 @@ async function getApiResponse(page, { endpointId }) {
|
|
|
23
22
|
const state = await getApiState(page, endpointId);
|
|
24
23
|
return state?.response;
|
|
25
24
|
}
|
|
26
|
-
|
|
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 };
|
|
25
|
+
export { getApiState, getApiResponse };
|
package/dist/core/index.js
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
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
|
-
*/ export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
15
|
+
*/ export { getShortcutModifier } from './keyboard.js';
|
|
16
|
+
export { getBlock } from './locators.js';
|
|
17
|
+
export { goto, waitForReady, waitForPage } from './navigation.js';
|
|
18
|
+
export { getRequestState, getRequestResponse } from './requests.js';
|
|
19
|
+
export { getState, getBlockState, setState } from './state.js';
|
|
20
|
+
export { getValidation } from './validation.js';
|
|
21
|
+
export { setUrlQuery } from './url.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
*/ // A `mod` shortcut is resolved by tinykeys, which reads navigator.platform in the browser. That is
|
|
16
|
+
// the platform Playwright emulates for the project's device, not the one the test runner is on — a
|
|
17
|
+
// Desktop Chrome project reports Win32 even on macOS. Reading it from the page is what keeps the key
|
|
18
|
+
// a test presses the same key the app is listening for. Mirrors tinykeys' own check.
|
|
19
|
+
async function getShortcutModifier(page) {
|
|
20
|
+
const isApple = await page.evaluate(()=>/Mac|iPod|iPhone|iPad/.test(navigator.platform));
|
|
21
|
+
return isApple ? 'Meta' : 'Control';
|
|
22
|
+
}
|
|
23
|
+
export { getShortcutModifier };
|
package/dist/core/navigation.js
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
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
|
-
async function waitForReady(page) {
|
|
15
|
+
*/ async function waitForReady(page) {
|
|
17
16
|
// Wait for Lowdefy client to initialize (context exists)
|
|
18
17
|
// Context key is "page:{pageId}" not just pageId
|
|
19
18
|
await page.waitForFunction(()=>{
|
|
@@ -32,13 +31,10 @@ async function goto(page, path) {
|
|
|
32
31
|
});
|
|
33
32
|
await waitForReady(page);
|
|
34
33
|
}
|
|
35
|
-
async function expectNavigation(page, urlPattern) {
|
|
36
|
-
await expect(page).toHaveURL(urlPattern);
|
|
37
|
-
}
|
|
38
34
|
async function waitForPage(page, path) {
|
|
39
35
|
await page.waitForURL(path, {
|
|
40
36
|
waitUntil: 'domcontentloaded'
|
|
41
37
|
});
|
|
42
38
|
await waitForReady(page);
|
|
43
39
|
}
|
|
44
|
-
export { goto, waitForReady,
|
|
40
|
+
export { goto, waitForReady, waitForPage };
|
package/dist/core/requests.js
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
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
|
-
async function getRequestState(page, requestId) {
|
|
15
|
+
*/ async function getRequestState(page, requestId) {
|
|
17
16
|
return page.evaluate((reqId)=>{
|
|
18
17
|
const lowdefy = window.lowdefy;
|
|
19
18
|
const pageId = lowdefy?.pageId;
|
|
@@ -25,32 +24,4 @@ async function getRequestResponse(page, { requestId }) {
|
|
|
25
24
|
const state = await getRequestState(page, requestId);
|
|
26
25
|
return state?.response;
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
if (loading !== undefined) {
|
|
30
|
-
await expect.poll(async ()=>{
|
|
31
|
-
const state = await getRequestState(page, requestId);
|
|
32
|
-
return state?.loading;
|
|
33
|
-
}, {
|
|
34
|
-
timeout
|
|
35
|
-
}).toBe(loading);
|
|
36
|
-
}
|
|
37
|
-
if (response !== undefined) {
|
|
38
|
-
await expect.poll(async ()=>{
|
|
39
|
-
const state = await getRequestState(page, requestId);
|
|
40
|
-
return state?.response;
|
|
41
|
-
}, {
|
|
42
|
-
timeout
|
|
43
|
-
}).toEqual(expect.objectContaining(response));
|
|
44
|
-
}
|
|
45
|
-
// Payload is stored in the request state by the Lowdefy engine.
|
|
46
|
-
// It's the evaluated payload from the request config (client-side).
|
|
47
|
-
if (payload !== undefined) {
|
|
48
|
-
await expect.poll(async ()=>{
|
|
49
|
-
const state = await getRequestState(page, requestId);
|
|
50
|
-
return state?.payload;
|
|
51
|
-
}, {
|
|
52
|
-
timeout
|
|
53
|
-
}).toEqual(expect.objectContaining(payload));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
export { getRequestState, getRequestResponse, expectRequest };
|
|
27
|
+
export { getRequestState, getRequestResponse };
|
package/dist/core/state.js
CHANGED
|
@@ -12,9 +12,7 @@
|
|
|
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
|
-
import { get } from '@lowdefy/helpers';
|
|
17
|
-
async function getState(page) {
|
|
15
|
+
*/ async function getState(page) {
|
|
18
16
|
return page.evaluate(()=>{
|
|
19
17
|
const lowdefy = window.lowdefy;
|
|
20
18
|
const pageId = lowdefy?.pageId;
|
|
@@ -26,26 +24,18 @@ async function getBlockState(page, { blockId }) {
|
|
|
26
24
|
return state?.[blockId];
|
|
27
25
|
}
|
|
28
26
|
async function setState(page, { key, value }) {
|
|
27
|
+
// context.methods.setState does not exist on engine contexts - the engine's
|
|
28
|
+
// action layer (see createSetState.js) writes state via State.set() and then
|
|
29
|
+
// triggers a re-render via context._internal.update().
|
|
29
30
|
await page.evaluate(({ k, v })=>{
|
|
30
31
|
const lowdefy = window.lowdefy;
|
|
31
32
|
const pageId = lowdefy?.pageId;
|
|
32
33
|
const context = lowdefy?.contexts?.[`page:${pageId}`];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
[k]: v
|
|
36
|
-
});
|
|
37
|
-
}
|
|
34
|
+
context._internal.State.set(k, v);
|
|
35
|
+
context._internal.update();
|
|
38
36
|
}, {
|
|
39
37
|
k: key,
|
|
40
38
|
v: value
|
|
41
39
|
});
|
|
42
40
|
}
|
|
43
|
-
|
|
44
|
-
await expect.poll(async ()=>{
|
|
45
|
-
const state = await getState(page);
|
|
46
|
-
return get(state, key);
|
|
47
|
-
}, {
|
|
48
|
-
timeout
|
|
49
|
-
}).toEqual(value);
|
|
50
|
-
}
|
|
51
|
-
export { getState, getBlockState, setState, expectState };
|
|
41
|
+
export { getState, getBlockState, setState };
|
package/dist/core/url.js
CHANGED
|
@@ -12,35 +12,78 @@
|
|
|
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
|
-
*/ import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
import { waitForReady } from './navigation.js';
|
|
17
|
+
// The client router's createUrl prepends basePath, so the pathname handed to it
|
|
18
|
+
// must have basePath stripped or a basePath app receives it twice.
|
|
19
|
+
function createTargetLocation({ basePath = '', href, key, value }) {
|
|
20
|
+
const url = new URL(href);
|
|
21
|
+
if (type.isNone(value)) {
|
|
22
|
+
url.searchParams.delete(key);
|
|
23
|
+
} else {
|
|
24
|
+
url.searchParams.set(key, value);
|
|
19
25
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
await expect.poll(async ()=>{
|
|
26
|
-
const url = new URL(page.url());
|
|
27
|
-
return url.searchParams.get(key);
|
|
28
|
-
}, {
|
|
29
|
-
timeout
|
|
30
|
-
}).toBe(value);
|
|
26
|
+
const pathname = basePath && url.pathname.startsWith(basePath) ? url.pathname.slice(basePath.length) : url.pathname;
|
|
27
|
+
return {
|
|
28
|
+
pathname,
|
|
29
|
+
query: url.searchParams.toString()
|
|
30
|
+
};
|
|
31
31
|
}
|
|
32
|
+
// Navigates through the app's own router rather than calling history.pushState
|
|
33
|
+
// directly. A bare pushState updates the URL without notifying the router, so
|
|
34
|
+
// the page config is never re-fetched and Dynamic pages keep serving content
|
|
35
|
+
// resolved from the previous urlQuery. Routing through the router is what a
|
|
36
|
+
// Link or SetUrlQuery action in the app does.
|
|
32
37
|
async function setUrlQuery(page, { key, value }) {
|
|
33
|
-
await page.evaluate((
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
url.searchParams.set(k, v);
|
|
38
|
+
const { basePath, dynamic, href } = await page.evaluate(()=>{
|
|
39
|
+
const lowdefy = window.lowdefy;
|
|
40
|
+
const router = lowdefy?._internal?.router;
|
|
41
|
+
if (!router) {
|
|
42
|
+
throw new Error('Lowdefy client not initialized. Call goto() before setting urlQuery.');
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
return {
|
|
45
|
+
basePath: router.basePath ?? '',
|
|
46
|
+
dynamic: lowdefy.contexts[`page:${lowdefy.pageId}`]?._internal?.pageConfig?.dynamic === true,
|
|
47
|
+
href: window.location.href
|
|
48
|
+
};
|
|
44
49
|
});
|
|
50
|
+
// A router navigation always re-fetches the page config, and blocks reading
|
|
51
|
+
// _url_query re-render only once that config is applied. Listening starts
|
|
52
|
+
// before the navigation so the response cannot be missed.
|
|
53
|
+
const pageConfigFetched = page.waitForResponse((response)=>response.url().includes('/api/page/'), {
|
|
54
|
+
timeout: 30000
|
|
55
|
+
}).catch((error)=>{
|
|
56
|
+
// A response that never arrives must not hang the suite — fall through and
|
|
57
|
+
// let the test's own assertion report it. Anything else is a real fault.
|
|
58
|
+
if (error.name !== 'TimeoutError') throw error;
|
|
59
|
+
});
|
|
60
|
+
// A Dynamic page's content is resolved server-side from urlQuery, and the
|
|
61
|
+
// engine rebuilds its context whenever a new config object arrives (see
|
|
62
|
+
// getContext). Waiting for that rebuild proves the re-resolved content is in
|
|
63
|
+
// place; the response arriving only proves it is on its way. Static pages keep
|
|
64
|
+
// a memoized context, so there is no identity change to wait for.
|
|
65
|
+
const previousPageConfig = dynamic ? await page.evaluateHandle(()=>window.lowdefy.contexts[`page:${window.lowdefy.pageId}`]._internal.pageConfig) : null;
|
|
66
|
+
await page.evaluate(({ pathname, query })=>{
|
|
67
|
+
// scroll: false keeps the viewport where it was — a query change is not a
|
|
68
|
+
// page navigation from the test's point of view.
|
|
69
|
+
window.lowdefy._internal.router.push({
|
|
70
|
+
pathname,
|
|
71
|
+
query,
|
|
72
|
+
scroll: false
|
|
73
|
+
});
|
|
74
|
+
}, createTargetLocation({
|
|
75
|
+
basePath,
|
|
76
|
+
href,
|
|
77
|
+
key,
|
|
78
|
+
value
|
|
79
|
+
}));
|
|
80
|
+
await pageConfigFetched;
|
|
81
|
+
if (previousPageConfig) {
|
|
82
|
+
await page.waitForFunction((previous)=>window.lowdefy.contexts[`page:${window.lowdefy.pageId}`]?._internal?.pageConfig !== previous, previousPageConfig, {
|
|
83
|
+
timeout: 30000
|
|
84
|
+
});
|
|
85
|
+
await previousPageConfig.dispose();
|
|
86
|
+
}
|
|
87
|
+
await waitForReady(page);
|
|
45
88
|
}
|
|
46
|
-
export {
|
|
89
|
+
export { createTargetLocation, setUrlQuery };
|
package/dist/core/validation.js
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
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
|
-
async function getValidation(page, blockId) {
|
|
15
|
+
*/ async function getValidation(page, blockId) {
|
|
17
16
|
return page.evaluate((id)=>{
|
|
18
17
|
const lowdefy = window.lowdefy;
|
|
19
18
|
const pageId = lowdefy?.pageId;
|
|
@@ -22,51 +21,4 @@ async function getValidation(page, blockId) {
|
|
|
22
21
|
return block?.validationEval?.output;
|
|
23
22
|
}, blockId);
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
await expect.poll(async ()=>{
|
|
27
|
-
const validation = await getValidation(page, blockId);
|
|
28
|
-
return validation?.status;
|
|
29
|
-
}, {
|
|
30
|
-
timeout
|
|
31
|
-
}).toBe(status);
|
|
32
|
-
}
|
|
33
|
-
async function expectValidationError(page, { blockId, message, timeout = 5000 }) {
|
|
34
|
-
await expect.poll(async ()=>{
|
|
35
|
-
const validation = await getValidation(page, blockId);
|
|
36
|
-
if (validation?.status !== 'error') return {
|
|
37
|
-
status: validation?.status
|
|
38
|
-
};
|
|
39
|
-
if (message && !validation?.errors?.includes(message)) {
|
|
40
|
-
return {
|
|
41
|
-
errors: validation?.errors
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
return true;
|
|
45
|
-
}, {
|
|
46
|
-
timeout
|
|
47
|
-
}).toBe(true);
|
|
48
|
-
}
|
|
49
|
-
async function expectValidationWarning(page, { blockId, message, timeout = 5000 }) {
|
|
50
|
-
await expect.poll(async ()=>{
|
|
51
|
-
const validation = await getValidation(page, blockId);
|
|
52
|
-
if (validation?.status !== 'warning') return {
|
|
53
|
-
status: validation?.status
|
|
54
|
-
};
|
|
55
|
-
if (message && !validation?.warnings?.includes(message)) {
|
|
56
|
-
return {
|
|
57
|
-
warnings: validation?.warnings
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return true;
|
|
61
|
-
}, {
|
|
62
|
-
timeout
|
|
63
|
-
}).toBe(true);
|
|
64
|
-
}
|
|
65
|
-
async function expectValidationSuccess(page, { blockId, timeout = 5000 }) {
|
|
66
|
-
await expectValidationStatus(page, {
|
|
67
|
-
blockId,
|
|
68
|
-
status: 'success',
|
|
69
|
-
timeout
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
export { getValidation, expectValidationStatus, expectValidationError, expectValidationWarning, expectValidationSuccess };
|
|
24
|
+
export { getValidation };
|
package/dist/fixtures/index.js
CHANGED
|
@@ -20,6 +20,20 @@ import createPageManager from '../proxy/createPageManager.js';
|
|
|
20
20
|
import { generateManifest, loadManifest } from '../testPrep/generateManifest.js';
|
|
21
21
|
import { createMockManager, loadStaticMocks } from '../mocking/index.js';
|
|
22
22
|
import { setUserCookie } from '../core/userCookie.js';
|
|
23
|
+
import { expectState } from '../assertions/state.js';
|
|
24
|
+
import { expectRequest } from '../assertions/requests.js';
|
|
25
|
+
import { expectApi } from '../assertions/api.js';
|
|
26
|
+
import { expectUrl, expectUrlQuery } from '../assertions/url.js';
|
|
27
|
+
// The Playwright `expect` implementations createPageManager needs for its
|
|
28
|
+
// `.expect.*` methods - injected here so createPageManager itself (exported from
|
|
29
|
+
// the "./runtime" subpath too) never has to import @playwright/test.
|
|
30
|
+
const assertions = {
|
|
31
|
+
expectState,
|
|
32
|
+
expectRequest,
|
|
33
|
+
expectApi,
|
|
34
|
+
expectUrl,
|
|
35
|
+
expectUrlQuery
|
|
36
|
+
};
|
|
23
37
|
const DEFAULT_BUILD_DIR = '.lowdefy/server/build';
|
|
24
38
|
// Create test with ldf fixture
|
|
25
39
|
export const test = base.extend({
|
|
@@ -149,7 +163,8 @@ export const test = base.extend({
|
|
|
149
163
|
page,
|
|
150
164
|
manifest,
|
|
151
165
|
helperRegistry,
|
|
152
|
-
mockManager
|
|
166
|
+
mockManager,
|
|
167
|
+
assertions
|
|
153
168
|
});
|
|
154
169
|
await use(pageManager);
|
|
155
170
|
await mockManager.cleanup();
|
package/dist/index.js
CHANGED
|
@@ -12,21 +12,34 @@
|
|
|
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
|
-
*/ // Core helpers
|
|
15
|
+
*/ // Core helpers (runner-agnostic - also available from the "./runtime" subpath)
|
|
16
16
|
import { escapeId } from './core/escapeId.js';
|
|
17
|
+
import { getShortcutModifier } from './core/keyboard.js';
|
|
17
18
|
import { getBlock } from './core/locators.js';
|
|
18
|
-
import { goto, waitForReady,
|
|
19
|
-
import { getRequestState, getRequestResponse
|
|
20
|
-
import { getState, getBlockState, setState
|
|
21
|
-
import { getValidation
|
|
22
|
-
import {
|
|
19
|
+
import { goto, waitForReady, waitForPage } from './core/navigation.js';
|
|
20
|
+
import { getRequestState, getRequestResponse } from './core/requests.js';
|
|
21
|
+
import { getState, getBlockState, setState } from './core/state.js';
|
|
22
|
+
import { getValidation } from './core/validation.js';
|
|
23
|
+
import { setUrlQuery } from './core/url.js';
|
|
23
24
|
export { escapeId };
|
|
25
|
+
export { getShortcutModifier };
|
|
24
26
|
export { getBlock };
|
|
25
|
-
export { goto, waitForReady,
|
|
26
|
-
export { getRequestState, getRequestResponse
|
|
27
|
-
export { getState, getBlockState, setState
|
|
28
|
-
export { getValidation
|
|
29
|
-
export {
|
|
27
|
+
export { goto, waitForReady, waitForPage };
|
|
28
|
+
export { getRequestState, getRequestResponse };
|
|
29
|
+
export { getState, getBlockState, setState };
|
|
30
|
+
export { getValidation };
|
|
31
|
+
export { setUrlQuery };
|
|
32
|
+
// Assertions (coupled to @playwright/test)
|
|
33
|
+
import { expectNavigation } from './assertions/navigation.js';
|
|
34
|
+
import { expectRequest } from './assertions/requests.js';
|
|
35
|
+
import { expectState } from './assertions/state.js';
|
|
36
|
+
import { expectValidationStatus, expectValidationError, expectValidationWarning, expectValidationSuccess } from './assertions/validation.js';
|
|
37
|
+
import { expectUrl, expectUrlQuery } from './assertions/url.js';
|
|
38
|
+
export { expectNavigation };
|
|
39
|
+
export { expectRequest };
|
|
40
|
+
export { expectState };
|
|
41
|
+
export { expectValidationStatus, expectValidationError, expectValidationWarning, expectValidationSuccess };
|
|
42
|
+
export { expectUrl, expectUrlQuery };
|
|
30
43
|
// Test prep utilities
|
|
31
44
|
import { generateManifest, loadManifest } from './testPrep/generateManifest.js';
|
|
32
45
|
export { generateManifest, loadManifest };
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { expect } from '@playwright/test';
|
|
16
|
-
import { expectValidationError, expectValidationWarning, expectValidationSuccess } from '../
|
|
16
|
+
import { expectValidationError, expectValidationWarning, expectValidationSuccess } from '../assertions/validation.js';
|
|
17
17
|
// Each block's e2e helper defines a `locator` function that targets the block element on the page.
|
|
18
18
|
// Two locator patterns exist:
|
|
19
19
|
// - `#${escapeId(blockId)}` — for blocks that render `id={blockId}` on their root DOM element
|
|
@@ -14,16 +14,26 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { getBlock } from '../core/locators.js';
|
|
16
16
|
import { waitForReady, waitForPage } from '../core/navigation.js';
|
|
17
|
-
import { getState, getBlockState, setState
|
|
18
|
-
import { getRequestState, getRequestResponse
|
|
19
|
-
import { getApiState, getApiResponse
|
|
17
|
+
import { getState, getBlockState, setState } from '../core/state.js';
|
|
18
|
+
import { getRequestState, getRequestResponse } from '../core/requests.js';
|
|
19
|
+
import { getApiState, getApiResponse } from '../core/api.js';
|
|
20
20
|
import { getValidation } from '../core/validation.js';
|
|
21
|
-
import {
|
|
21
|
+
import { setUrlQuery } from '../core/url.js';
|
|
22
22
|
import { setUserCookie, clearUserCookie } from '../core/userCookie.js';
|
|
23
23
|
import { get, type } from '@lowdefy/helpers';
|
|
24
24
|
import createBlockMethodProxy from './createBlockMethodProxy.js';
|
|
25
25
|
import resolveTemplateId from './resolveTemplateId.js';
|
|
26
|
-
|
|
26
|
+
// createPageManager itself stays free of @playwright/test (it is exported from the
|
|
27
|
+
// "./runtime" subpath). The Playwright-backed `expect*` functions are injected via
|
|
28
|
+
// `assertions` by the caller - the "./fixtures" ldf fixture wires these automatically.
|
|
29
|
+
function requireAssertion(fn, name) {
|
|
30
|
+
if (type.isUndefined(fn)) {
|
|
31
|
+
throw new Error(`createPageManager: "${name}" assertion is not available on this instance. ` + `Import { ${name} } from "@lowdefy/e2e-utils" and pass it as assertions.${name}, ` + `or use the "@lowdefy/e2e-utils/fixtures" ldf fixture, which wires assertions automatically.`);
|
|
32
|
+
}
|
|
33
|
+
return fn;
|
|
34
|
+
}
|
|
35
|
+
function createPageManager({ page, manifest, helperRegistry, mockManager, assertions = {} }) {
|
|
36
|
+
const { expectState, expectRequest, expectApi, expectUrl, expectUrlQuery } = assertions;
|
|
27
37
|
let currentBlockMap = null;
|
|
28
38
|
let currentPageId = null;
|
|
29
39
|
function ensurePageLoaded() {
|
|
@@ -142,17 +152,17 @@ function createPageManager({ page, manifest, helperRegistry, mockManager }) {
|
|
|
142
152
|
request (requestId) {
|
|
143
153
|
return {
|
|
144
154
|
expect: {
|
|
145
|
-
toFinish: (opts)=>expectRequest(page, {
|
|
155
|
+
toFinish: (opts)=>requireAssertion(expectRequest, 'expectRequest')(page, {
|
|
146
156
|
requestId,
|
|
147
157
|
loading: false,
|
|
148
158
|
...opts
|
|
149
159
|
}),
|
|
150
|
-
toHaveResponse: (response, opts)=>expectRequest(page, {
|
|
160
|
+
toHaveResponse: (response, opts)=>requireAssertion(expectRequest, 'expectRequest')(page, {
|
|
151
161
|
requestId,
|
|
152
162
|
response,
|
|
153
163
|
...opts
|
|
154
164
|
}),
|
|
155
|
-
toHavePayload: (payload, opts)=>expectRequest(page, {
|
|
165
|
+
toHavePayload: (payload, opts)=>requireAssertion(expectRequest, 'expectRequest')(page, {
|
|
156
166
|
requestId,
|
|
157
167
|
payload,
|
|
158
168
|
...opts
|
|
@@ -168,17 +178,17 @@ function createPageManager({ page, manifest, helperRegistry, mockManager }) {
|
|
|
168
178
|
api (endpointId) {
|
|
169
179
|
return {
|
|
170
180
|
expect: {
|
|
171
|
-
toFinish: (opts)=>expectApi(page, {
|
|
181
|
+
toFinish: (opts)=>requireAssertion(expectApi, 'expectApi')(page, {
|
|
172
182
|
endpointId,
|
|
173
183
|
loading: false,
|
|
174
184
|
...opts
|
|
175
185
|
}),
|
|
176
|
-
toHaveResponse: (response, opts)=>expectApi(page, {
|
|
186
|
+
toHaveResponse: (response, opts)=>requireAssertion(expectApi, 'expectApi')(page, {
|
|
177
187
|
endpointId,
|
|
178
188
|
response,
|
|
179
189
|
...opts
|
|
180
190
|
}),
|
|
181
|
-
toHavePayload: (payload, opts)=>expectApi(page, {
|
|
191
|
+
toHavePayload: (payload, opts)=>requireAssertion(expectApi, 'expectApi')(page, {
|
|
182
192
|
endpointId,
|
|
183
193
|
payload,
|
|
184
194
|
...opts
|
|
@@ -205,7 +215,7 @@ function createPageManager({ page, manifest, helperRegistry, mockManager }) {
|
|
|
205
215
|
})
|
|
206
216
|
},
|
|
207
217
|
expect: {
|
|
208
|
-
toBe: (value, opts)=>expectState(page, {
|
|
218
|
+
toBe: (value, opts)=>requireAssertion(expectState, 'expectState')(page, {
|
|
209
219
|
key,
|
|
210
220
|
value,
|
|
211
221
|
...opts
|
|
@@ -218,11 +228,11 @@ function createPageManager({ page, manifest, helperRegistry, mockManager }) {
|
|
|
218
228
|
url () {
|
|
219
229
|
return {
|
|
220
230
|
expect: {
|
|
221
|
-
toBe: (path, opts)=>expectUrl(page, {
|
|
231
|
+
toBe: (path, opts)=>requireAssertion(expectUrl, 'expectUrl')(page, {
|
|
222
232
|
url: path,
|
|
223
233
|
...opts
|
|
224
234
|
}),
|
|
225
|
-
toMatch: (pattern, opts)=>expectUrl(page, {
|
|
235
|
+
toMatch: (pattern, opts)=>requireAssertion(expectUrl, 'expectUrl')(page, {
|
|
226
236
|
url: pattern,
|
|
227
237
|
...opts
|
|
228
238
|
})
|
|
@@ -240,7 +250,7 @@ function createPageManager({ page, manifest, helperRegistry, mockManager }) {
|
|
|
240
250
|
})
|
|
241
251
|
},
|
|
242
252
|
expect: {
|
|
243
|
-
toBe: (value, opts)=>expectUrlQuery(page, {
|
|
253
|
+
toBe: (value, opts)=>requireAssertion(expectUrlQuery, 'expectUrlQuery')(page, {
|
|
244
254
|
key,
|
|
245
255
|
value,
|
|
246
256
|
...opts
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
*/ // Runner-agnostic surface: reading/writing a Lowdefy Page via the Playwright
|
|
16
|
+
// `page` interface, with no dependency on @playwright/test. Useful for driving
|
|
17
|
+
// a page from tooling that isn't a Playwright test run (e.g. headless
|
|
18
|
+
// inspection). The `expect*` assertion helpers live in "./assertions" and the
|
|
19
|
+
// full test-authoring API (which does depend on @playwright/test) is exported
|
|
20
|
+
// from "." and "./fixtures".
|
|
21
|
+
export { escapeId } from './core/escapeId.js';
|
|
22
|
+
export { getShortcutModifier } from './core/keyboard.js';
|
|
23
|
+
export { getBlock } from './core/locators.js';
|
|
24
|
+
export { goto, waitForReady, waitForPage } from './core/navigation.js';
|
|
25
|
+
export { getRequestState, getRequestResponse } from './core/requests.js';
|
|
26
|
+
export { getApiState, getApiResponse } from './core/api.js';
|
|
27
|
+
export { getState, getBlockState, setState } from './core/state.js';
|
|
28
|
+
export { getValidation } from './core/validation.js';
|
|
29
|
+
export { setUrlQuery } from './core/url.js';
|
|
30
|
+
export { setUserCookie, clearUserCookie } from './core/userCookie.js';
|
|
31
|
+
export { generateManifest, loadManifest } from './testPrep/generateManifest.js';
|
|
32
|
+
export { createMockManager, loadStaticMocks } from './mocking/index.js';
|
|
33
|
+
export { default as createPageManager } from './proxy/createPageManager.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/e2e-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Lowdefy E2E Testing Utilities for Playwright",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -30,22 +30,23 @@
|
|
|
30
30
|
"exports": {
|
|
31
31
|
".": "./dist/index.js",
|
|
32
32
|
"./config": "./dist/config.js",
|
|
33
|
-
"./fixtures": "./dist/fixtures/index.js"
|
|
33
|
+
"./fixtures": "./dist/fixtures/index.js",
|
|
34
|
+
"./runtime": "./dist/runtime.js"
|
|
34
35
|
},
|
|
35
36
|
"files": [
|
|
36
37
|
"dist/*"
|
|
37
38
|
],
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@lowdefy/helpers": "
|
|
40
|
+
"@lowdefy/helpers": "6.0.0",
|
|
40
41
|
"js-yaml": "4.1.0",
|
|
41
42
|
"prompts": "2.4.2"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
|
-
"@playwright/test": "1.
|
|
45
|
+
"@playwright/test": "1.59.1"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
|
-
"@swc/cli": "0.8.
|
|
48
|
-
"@swc/core": "1.15.
|
|
48
|
+
"@swc/cli": "0.8.1",
|
|
49
|
+
"@swc/core": "1.15.32",
|
|
49
50
|
"copyfiles": "2.4.1",
|
|
50
51
|
"jest": "28.1.3"
|
|
51
52
|
},
|