cabloy 5.1.159 → 5.1.160
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/.cabloy-version +1 -1
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/repo-docs/.vitepress/config.mjs +11 -0
- package/repo-docs/backend/department-management.md +98 -0
- package/repo-docs/backend/menu-authorization.md +124 -0
- package/repo-docs/backend/rbac-authorization.md +117 -0
- package/repo-docs/backend/resource-field-update.md +2 -2
- package/repo-docs/backend/role-management.md +92 -0
- package/repo-docs/backend/shared-rbac-architecture.md +339 -0
- package/repo-docs/backend/user-management.md +92 -0
- package/repo-docs/frontend/model-resource-owner-pattern.md +2 -0
- package/repo-docs/frontend/permission-formscene-action-visibility-guide.md +5 -3
- package/repo-docs/frontend/table-action-visibility-permission-flow-guide.md +2 -0
- package/repo-docs/frontend/table-guide.md +75 -22
- package/repo-docs/frontend/table-resource-crud-cookbook.md +25 -4
- package/repo-docs/frontend/zova-table-source-reading-map.md +54 -19
- package/repo-docs/frontend/zova-table-under-the-hood.md +70 -39
- package/test-results/.last-run.json +20 -2
- package/test-results/a-commerce-ATP-ADDR-01-aut-11dea-ss-through-Web-self-service/error-context.md +238 -0
- package/test-results/a-commerce-ATP-SPC-01-Coup-ef380-mantic-Admin-field-controls/error-context.md +240 -0
- package/test-results/a-commerce-ATP-SPC-02-Cate-9f120-on-and-publication-controls/error-context.md +238 -0
- package/test-results/a-commerce-ATP-SPC-02-Prod-5f92a-on-and-publication-controls/error-context.md +238 -0
- package/test-results/a-commerce-ATP-SPC-02-SKU--2e61e-ency-and-lifecycle-controls/error-context.md +238 -0
- package/test-results/a-commerce-ATP-SPC-04-Stoc-fa895--readonly-and-mutation-free/error-context.md +235 -0
- package/test-results/a-commerce-ATP-SPC-05-syst-fb406-e-without-mutation-controls/error-context.md +238 -0
- package/test-results/a-commerce-Commerce-sessio-4e963-ie-selects-raw-server-theme/error-context.md +226 -0
- package/test-results/a-commerce-Commerce-theme--062fe--without-hydration-mismatch/error-context.md +229 -0
- package/test-results/a-commerce-PayPal-browser--8672c-t-or-open-an-awaiting-order/error-context.md +238 -0
- package/test-results/a-commerce-Payment-callbac-f56c2--reconciles-after-hydration/error-context.md +238 -0
- package/test-results/a-commerce-Payment-cancell-3fc74-ified-provider-confirmation/error-context.md +238 -0
- package/test-results/a-commerce-Phase-50-60-aut-71d61--observes-operator-shipment/error-context.md +238 -0
- package/test-results/a-commerce-Phase-60-custom-407cd-ecutes-a-whole-order-refund/error-context.md +238 -0
- package/test-results/cabloy-basic-ATP-BASIC-TAB-e7253-and-pins-configured-columns/error-context.md +240 -0
- package/test-results/markdown-ATP-SPC-02-Produc-9d5c2-link-toolbar-edits-Markdown/error-context.md +211 -0
- package/test-results/markdown-ATP-SPC-02-Produc-9d6ea-t-editor-and-saves-Markdown/error-context.md +238 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/pnpm-lock.yaml +97 -11
- package/zova/pnpm-lock.yaml +16 -16
package/test-results/a-commerce-Commerce-theme--062fe--without-hydration-mismatch/error-context.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Instructions
|
|
2
|
+
|
|
3
|
+
- Following Playwright test failed.
|
|
4
|
+
- Explain why, be concise, respect Playwright best practices.
|
|
5
|
+
- Provide a snippet of code with the fix, if possible.
|
|
6
|
+
|
|
7
|
+
# Test info
|
|
8
|
+
|
|
9
|
+
- Name: a-commerce.spec.ts >> Commerce theme: session cookie mirrors public local preference without hydration mismatch
|
|
10
|
+
- Location: repo-e2e/specs/a-commerce.spec.ts:287:1
|
|
11
|
+
|
|
12
|
+
# Error details
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Error: expect(received).toBe(expected) // Object.is equality
|
|
16
|
+
|
|
17
|
+
Expected: "false"
|
|
18
|
+
Received: null
|
|
19
|
+
|
|
20
|
+
Call Log:
|
|
21
|
+
- Timeout 5000ms exceeded while waiting on the predicate
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
# Test source
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
214 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
28
|
+
215 | }
|
|
29
|
+
216 |
|
|
30
|
+
217 | test(
|
|
31
|
+
218 | 'ATP-SSR-01: anonymous Commerce HTML is public and hydration completes',
|
|
32
|
+
219 | { tag: ['@web', '@smoke'] },
|
|
33
|
+
220 | async ({ page, request }) => {
|
|
34
|
+
221 | const response = await request.get('/commerce');
|
|
35
|
+
222 | expect(response.ok()).toBeTruthy();
|
|
36
|
+
223 | expect(response.headers()['cache-control']).toBe('no-cache, no-store, must-revalidate');
|
|
37
|
+
224 | const html = await response.text();
|
|
38
|
+
225 | const normalizedHtml = html.toLowerCase();
|
|
39
|
+
226 | expect(normalizedHtml).not.toContain('data-zova-hydrated');
|
|
40
|
+
227 |
|
|
41
|
+
228 | for (const marker of privateMarkers) {
|
|
42
|
+
229 | expect(normalizedHtml).not.toContain(`"${marker}"`);
|
|
43
|
+
230 | }
|
|
44
|
+
231 |
|
|
45
|
+
232 | const pageErrors = collectPageErrors(page);
|
|
46
|
+
233 | const documentResponse = await page.goto('/commerce', { waitUntil: 'load' });
|
|
47
|
+
234 | expect(documentResponse?.ok()).toBeTruthy();
|
|
48
|
+
235 | await expect(page).toHaveURL(/\/commerce(?:\/|$)/);
|
|
49
|
+
236 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
50
|
+
237 | await expect(page.getByRole('heading', { name: 'Commerce catalogue' })).toBeVisible();
|
|
51
|
+
238 | await expect(page.getByText('Dashboard')).toHaveCount(0);
|
|
52
|
+
239 | await expect(page.locator('body')).toBeVisible();
|
|
53
|
+
240 | await expect(page).not.toHaveTitle(/error/i);
|
|
54
|
+
241 | expect(pageErrors).toEqual([]);
|
|
55
|
+
242 | },
|
|
56
|
+
243 | );
|
|
57
|
+
244 |
|
|
58
|
+
245 | test(
|
|
59
|
+
246 | 'Commerce theme: public local preference mirrors session cookie without hydration mismatch',
|
|
60
|
+
247 | { tag: ['@web', '@ssr', '@theme'] },
|
|
61
|
+
248 | async ({ browser, request }, testInfo) => {
|
|
62
|
+
249 | const customer = await registerCustomer(request, testInfo);
|
|
63
|
+
250 | const context = await browser.newContext();
|
|
64
|
+
251 | await context.addInitScript(() => localStorage.setItem('themedark', 'true'));
|
|
65
|
+
252 | const page = await context.newPage();
|
|
66
|
+
253 | const pageErrors = collectPageErrors(page);
|
|
67
|
+
254 | const consoleErrors: string[] = [];
|
|
68
|
+
255 | page.on('console', message => {
|
|
69
|
+
256 | if (message.type() === 'error' || /hydration mismatch/i.test(message.text())) {
|
|
70
|
+
257 | consoleErrors.push(message.text());
|
|
71
|
+
258 | }
|
|
72
|
+
259 | });
|
|
73
|
+
260 | try {
|
|
74
|
+
261 | await page.goto('/commerce', { waitUntil: 'load' });
|
|
75
|
+
262 | await page.evaluate(() => localStorage.setItem('themedark', 'true'));
|
|
76
|
+
263 | await page.reload({ waitUntil: 'load' });
|
|
77
|
+
264 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
78
|
+
265 | await expect(page.locator('body')).toHaveAttribute('data-theme', 'dark');
|
|
79
|
+
266 | expect((await context.cookies()).find(cookie => cookie.name === 'themedark')?.value).toBe(
|
|
80
|
+
267 | 'true',
|
|
81
|
+
268 | );
|
|
82
|
+
269 |
|
|
83
|
+
270 | await login(
|
|
84
|
+
271 | page,
|
|
85
|
+
272 | '/commerce/commerce/member/address',
|
|
86
|
+
273 | customer.username,
|
|
87
|
+
274 | customer.password,
|
|
88
|
+
275 | 'commerce',
|
|
89
|
+
276 | );
|
|
90
|
+
277 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
91
|
+
278 | await expect(page.locator('body')).toHaveAttribute('data-theme', 'dark');
|
|
92
|
+
279 | expect(pageErrors).toEqual([]);
|
|
93
|
+
280 | expect(consoleErrors).toEqual([]);
|
|
94
|
+
281 | } finally {
|
|
95
|
+
282 | await context.close();
|
|
96
|
+
283 | }
|
|
97
|
+
284 | },
|
|
98
|
+
285 | );
|
|
99
|
+
286 |
|
|
100
|
+
287 | test(
|
|
101
|
+
288 | 'Commerce theme: session cookie mirrors public local preference without hydration mismatch',
|
|
102
|
+
289 | { tag: ['@web', '@ssr', '@theme'] },
|
|
103
|
+
290 | async ({ browser, request }, testInfo) => {
|
|
104
|
+
291 | const customer = await registerCustomer(request, testInfo);
|
|
105
|
+
292 | const context = await browser.newContext();
|
|
106
|
+
293 | const page = await context.newPage();
|
|
107
|
+
294 | const pageErrors = collectPageErrors(page);
|
|
108
|
+
295 | const consoleErrors = collectConsoleErrors(page);
|
|
109
|
+
296 | try {
|
|
110
|
+
297 | await login(
|
|
111
|
+
298 | page,
|
|
112
|
+
299 | '/commerce/commerce/member/address',
|
|
113
|
+
300 | customer.username,
|
|
114
|
+
301 | customer.password,
|
|
115
|
+
302 | 'commerce',
|
|
116
|
+
303 | );
|
|
117
|
+
304 | await context.addCookies([
|
|
118
|
+
305 | {
|
|
119
|
+
306 | name: 'themedark',
|
|
120
|
+
307 | value: 'false',
|
|
121
|
+
308 | url: new URL(page.url()).origin,
|
|
122
|
+
309 | },
|
|
123
|
+
310 | ]);
|
|
124
|
+
311 | await page.reload({ waitUntil: 'load' });
|
|
125
|
+
312 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
126
|
+
313 | await expect(page.locator('body')).toHaveAttribute('data-theme', 'light');
|
|
127
|
+
> 314 | await expect.poll(() => page.evaluate(() => localStorage.getItem('themedark'))).toBe('false');
|
|
128
|
+
| ^ Error: expect(received).toBe(expected) // Object.is equality
|
|
129
|
+
315 | expect(pageErrors).toEqual([]);
|
|
130
|
+
316 | expect(consoleErrors).toEqual([]);
|
|
131
|
+
317 |
|
|
132
|
+
318 | const publicContext = await browser.newContext();
|
|
133
|
+
319 | await publicContext.addInitScript(() => localStorage.setItem('themedark', 'false'));
|
|
134
|
+
320 | const publicPage = await publicContext.newPage();
|
|
135
|
+
321 | const publicPageErrors = collectPageErrors(publicPage);
|
|
136
|
+
322 | const publicConsoleErrors = collectConsoleErrors(publicPage);
|
|
137
|
+
323 | try {
|
|
138
|
+
324 | await publicPage.goto('/commerce', { waitUntil: 'load' });
|
|
139
|
+
325 | await expect(publicPage.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
140
|
+
326 | await expect(publicPage.locator('body')).toHaveAttribute('data-theme', 'light');
|
|
141
|
+
327 | await expect
|
|
142
|
+
328 | .poll(() => publicPage.evaluate(() => localStorage.getItem('themedark')))
|
|
143
|
+
329 | .toBe('false');
|
|
144
|
+
330 | expect(
|
|
145
|
+
331 | (await publicContext.cookies()).find(cookie => cookie.name === 'themedark')?.value,
|
|
146
|
+
332 | ).toBe('false');
|
|
147
|
+
333 | expect(publicPageErrors).toEqual([]);
|
|
148
|
+
334 | expect(publicConsoleErrors).toEqual([]);
|
|
149
|
+
335 | } finally {
|
|
150
|
+
336 | await publicContext.close();
|
|
151
|
+
337 | }
|
|
152
|
+
338 | } finally {
|
|
153
|
+
339 | await context.close();
|
|
154
|
+
340 | }
|
|
155
|
+
341 | },
|
|
156
|
+
342 | );
|
|
157
|
+
343 |
|
|
158
|
+
344 | test(
|
|
159
|
+
345 | 'Commerce theme: public auto preference mirrors resolved media value without hydration mismatch',
|
|
160
|
+
346 | { tag: ['@web', '@ssr', '@theme'] },
|
|
161
|
+
347 | async ({ browser }) => {
|
|
162
|
+
348 | const context = await browser.newContext();
|
|
163
|
+
349 | await context.addInitScript(() => localStorage.setItem('themedark', JSON.stringify('auto')));
|
|
164
|
+
350 | const page = await context.newPage();
|
|
165
|
+
351 | await page.emulateMedia({ colorScheme: 'dark' });
|
|
166
|
+
352 | const pageErrors = collectPageErrors(page);
|
|
167
|
+
353 | const consoleErrors = collectConsoleErrors(page);
|
|
168
|
+
354 | try {
|
|
169
|
+
355 | await page.goto('/commerce', { waitUntil: 'load' });
|
|
170
|
+
356 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
171
|
+
357 | await expect(page.locator('body')).toHaveAttribute('data-theme', 'dark');
|
|
172
|
+
358 | await expect
|
|
173
|
+
359 | .poll(() => page.evaluate(() => localStorage.getItem('themedark')))
|
|
174
|
+
360 | .toBe('"auto"');
|
|
175
|
+
361 | expect((await context.cookies()).find(cookie => cookie.name === 'themedark')?.value).toBe(
|
|
176
|
+
362 | 'true',
|
|
177
|
+
363 | );
|
|
178
|
+
364 | expect(pageErrors).toEqual([]);
|
|
179
|
+
365 | expect(consoleErrors).toEqual([]);
|
|
180
|
+
366 | } finally {
|
|
181
|
+
367 | await context.close();
|
|
182
|
+
368 | }
|
|
183
|
+
369 | },
|
|
184
|
+
370 | );
|
|
185
|
+
371 |
|
|
186
|
+
372 | test(
|
|
187
|
+
373 | 'Commerce public SSR: theme cookie does not change SSR HTML',
|
|
188
|
+
374 | { tag: ['@web', '@ssr', '@theme'] },
|
|
189
|
+
375 | async ({ request }) => {
|
|
190
|
+
376 | const responses = await Promise.all([
|
|
191
|
+
377 | request.get('/commerce'),
|
|
192
|
+
378 | request.get('/commerce', { headers: { Cookie: 'themedark=true' } }),
|
|
193
|
+
379 | request.get('/commerce', { headers: { Cookie: 'themedark=false' } }),
|
|
194
|
+
380 | ]);
|
|
195
|
+
381 | const html = await Promise.all(responses.map(response => response.text()));
|
|
196
|
+
382 | for (const [index, response] of responses.entries()) {
|
|
197
|
+
383 | expect(response.status(), `public response ${index}`).toBe(200);
|
|
198
|
+
384 | expect(response.headers()['cache-control'], `public response ${index}`).toBe(
|
|
199
|
+
385 | 'no-cache, no-store, must-revalidate',
|
|
200
|
+
386 | );
|
|
201
|
+
387 | expect(html[index].toLowerCase()).not.toContain('data-zova-hydrated');
|
|
202
|
+
388 | expect(html[index]).toContain('data-ssr-theme-dark-false="light"');
|
|
203
|
+
389 | expect(html[index]).toContain('data-ssr-theme-dark-true="dark"');
|
|
204
|
+
390 | expect(html[index]).not.toContain('ssr_cookie_themedark');
|
|
205
|
+
391 | expect(html[index]).toContain("window.matchMedia('(prefers-color-scheme: dark)')");
|
|
206
|
+
392 | expect(html[index]).not.toMatch(/<body[^>]*data-theme="(?:dark|light)"/);
|
|
207
|
+
393 | }
|
|
208
|
+
394 | },
|
|
209
|
+
395 | );
|
|
210
|
+
396 |
|
|
211
|
+
397 | test(
|
|
212
|
+
398 | 'Commerce session SSR: theme cookie selects raw server theme',
|
|
213
|
+
399 | { tag: ['@web', '@ssr', '@theme'] },
|
|
214
|
+
400 | async ({ browser, request }, testInfo) => {
|
|
215
|
+
401 | const customer = await registerCustomer(request, testInfo);
|
|
216
|
+
402 | const context = await browser.newContext();
|
|
217
|
+
403 | const page = await context.newPage();
|
|
218
|
+
404 | try {
|
|
219
|
+
405 | await login(
|
|
220
|
+
406 | page,
|
|
221
|
+
407 | '/commerce/commerce/member/address',
|
|
222
|
+
408 | customer.username,
|
|
223
|
+
409 | customer.password,
|
|
224
|
+
410 | 'commerce',
|
|
225
|
+
411 | );
|
|
226
|
+
412 | const origin = new URL(page.url()).origin;
|
|
227
|
+
413 | const authCookies = (await context.cookies()).filter(cookie => cookie.name !== 'themedark');
|
|
228
|
+
414 | const getSessionHtml = async (value: string) => {
|
|
229
|
+
```
|
package/test-results/a-commerce-PayPal-browser--8672c-t-or-open-an-awaiting-order/error-context.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Instructions
|
|
2
|
+
|
|
3
|
+
- Following Playwright test failed.
|
|
4
|
+
- Explain why, be concise, respect Playwright best practices.
|
|
5
|
+
- Provide a snippet of code with the fix, if possible.
|
|
6
|
+
|
|
7
|
+
# Test info
|
|
8
|
+
|
|
9
|
+
- Name: a-commerce.spec.ts >> PayPal browser cancel: customer can continue payment or open an awaiting order
|
|
10
|
+
- Location: repo-e2e/specs/a-commerce.spec.ts:943:1
|
|
11
|
+
|
|
12
|
+
# Error details
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Error: expect(page).toHaveURL(expected) failed
|
|
16
|
+
|
|
17
|
+
Expected pattern: /\/commerce\/commerce\/member\/address(?:\?|$)/
|
|
18
|
+
Received string: "http://127.0.0.1:7102/commerce/home/base/errorAccessDenied"
|
|
19
|
+
Timeout: 5000ms
|
|
20
|
+
|
|
21
|
+
Call log:
|
|
22
|
+
- Expect "toHaveURL" with timeout 5000ms
|
|
23
|
+
14 × locator resolved to <html lang="en-us" data-zova-hydrated="commerce">…</html>
|
|
24
|
+
- unexpected value "http://127.0.0.1:7102/commerce/home/base/errorAccessDenied"
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
- text: 403 Access denied.
|
|
30
|
+
- button "Go Home"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
# Test source
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
89 | await expect(passwordInput).toHaveValue(password);
|
|
37
|
+
90 | await expect(page.getByPlaceholder('Please input captcha')).not.toHaveValue('');
|
|
38
|
+
91 | const loginResponse = waitForApiResponse(page, 'POST', '/api/home/user/passport/login');
|
|
39
|
+
92 | await page.getByRole('button', { name: 'Login', exact: true }).click();
|
|
40
|
+
93 | const loginResponseValue = await loginResponse;
|
|
41
|
+
94 | if (!loginResponseValue.ok()) {
|
|
42
|
+
95 | throw new Error(
|
|
43
|
+
96 | `login failed: ${loginResponseValue.status()} ${await loginResponseValue.text()}`,
|
|
44
|
+
97 | );
|
|
45
|
+
98 | }
|
|
46
|
+
99 | await expect(page).not.toHaveURL(/\/login(?:\?|$)/);
|
|
47
|
+
100 | }
|
|
48
|
+
101 | }
|
|
49
|
+
102 |
|
|
50
|
+
103 | async function registerCustomer(
|
|
51
|
+
104 | request: APIRequestContext,
|
|
52
|
+
105 | testInfo: TestInfo,
|
|
53
|
+
106 | ): Promise<{ password: string; username: string }> {
|
|
54
|
+
107 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
55
|
+
108 | const username = `e2e-address-${id}`;
|
|
56
|
+
109 | const password = 'address-e2e-password';
|
|
57
|
+
110 | const captchaResponse = await request.post('/api/captcha/create', {
|
|
58
|
+
111 | data: { scene: 'captcha-simple:simple' },
|
|
59
|
+
112 | });
|
|
60
|
+
113 | expect(captchaResponse.ok()).toBeTruthy();
|
|
61
|
+
114 | const captcha = (await captchaResponse.json()).data;
|
|
62
|
+
115 | expect(captcha?.id).toEqual(expect.any(String));
|
|
63
|
+
116 | expect(captcha?.token).toEqual(expect.any(String));
|
|
64
|
+
117 |
|
|
65
|
+
118 | const baseURL = testInfo.project.use.baseURL;
|
|
66
|
+
119 | if (!baseURL) throw new Error('commerce E2E base URL is unavailable');
|
|
67
|
+
120 | const consumerUrl = new URL('/home/user/activation', baseURL).toString();
|
|
68
|
+
121 | const registerResponse = await request.post('/api/home/user/passport/register', {
|
|
69
|
+
122 | data: {
|
|
70
|
+
123 | username,
|
|
71
|
+
124 | email: `${username}@example.test`,
|
|
72
|
+
125 | password,
|
|
73
|
+
126 | passwordConfirm: password,
|
|
74
|
+
127 | consumerUrl,
|
|
75
|
+
128 | captcha: { id: captcha.id, token: captcha.token },
|
|
76
|
+
129 | },
|
|
77
|
+
130 | });
|
|
78
|
+
131 | expect(registerResponse.ok()).toBeTruthy();
|
|
79
|
+
132 | const registration = (await registerResponse.json()).data;
|
|
80
|
+
133 | expect(registration?.jwt?.accessToken).toEqual(expect.any(String));
|
|
81
|
+
134 | const activateResponse = await request.post(passportTestActivateCurrentPath, {
|
|
82
|
+
135 | headers: { Authorization: `Bearer ${registration.jwt.accessToken}` },
|
|
83
|
+
136 | });
|
|
84
|
+
137 | expect(activateResponse.ok()).toBeTruthy();
|
|
85
|
+
138 | return { username, password };
|
|
86
|
+
139 | }
|
|
87
|
+
140 |
|
|
88
|
+
141 | function createAddressFixture(testInfo: TestInfo): IAddressFixture {
|
|
89
|
+
142 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
90
|
+
143 | return {
|
|
91
|
+
144 | recipientName: `E2E Address ${id}`,
|
|
92
|
+
145 | phone: '15555550123',
|
|
93
|
+
146 | countryCode: 'US',
|
|
94
|
+
147 | region: 'California',
|
|
95
|
+
148 | city: `Original City ${id}`,
|
|
96
|
+
149 | postalCode: '94105',
|
|
97
|
+
150 | addressLine1: `1 Test Street ${id}`,
|
|
98
|
+
151 | addressLine2: 'Suite E2E',
|
|
99
|
+
152 | updatedCity: `Updated City ${id}`,
|
|
100
|
+
153 | };
|
|
101
|
+
154 | }
|
|
102
|
+
155 |
|
|
103
|
+
156 | function getAddressTextbox(page: Page, name: string): Locator {
|
|
104
|
+
157 | return page.getByRole('group', { name, exact: true }).getByRole('textbox');
|
|
105
|
+
158 | }
|
|
106
|
+
159 |
|
|
107
|
+
160 | async function fillAddressForm(page: Page, fixture: IAddressFixture) {
|
|
108
|
+
161 | await getAddressTextbox(page, 'Recipient Name *').fill(fixture.recipientName);
|
|
109
|
+
162 | await getAddressTextbox(page, 'Phone *').fill(fixture.phone);
|
|
110
|
+
163 | await getAddressTextbox(page, 'Country Code *').fill(fixture.countryCode);
|
|
111
|
+
164 | await getAddressTextbox(page, 'Region *').fill(fixture.region);
|
|
112
|
+
165 | await getAddressTextbox(page, 'City *').fill(fixture.city);
|
|
113
|
+
166 | await getAddressTextbox(page, 'Postal Code *').fill(fixture.postalCode);
|
|
114
|
+
167 | await getAddressTextbox(page, 'Address Line 1 *').fill(fixture.addressLine1);
|
|
115
|
+
168 | await getAddressTextbox(page, 'Address Line 2').fill(fixture.addressLine2);
|
|
116
|
+
169 | }
|
|
117
|
+
170 |
|
|
118
|
+
171 | async function createAddressThroughCustomerPage(
|
|
119
|
+
172 | browser: Browser,
|
|
120
|
+
173 | request: APIRequestContext,
|
|
121
|
+
174 | testInfo: TestInfo,
|
|
122
|
+
175 | ) {
|
|
123
|
+
176 | const customer = await registerCustomer(request, testInfo);
|
|
124
|
+
177 | const fixture = createAddressFixture(testInfo);
|
|
125
|
+
178 | const context = await browser.newContext();
|
|
126
|
+
179 | const page = await context.newPage();
|
|
127
|
+
180 | const pageErrors = collectPageErrors(page);
|
|
128
|
+
181 |
|
|
129
|
+
182 | await login(
|
|
130
|
+
183 | page,
|
|
131
|
+
184 | '/commerce/commerce/member/address',
|
|
132
|
+
185 | customer.username,
|
|
133
|
+
186 | customer.password,
|
|
134
|
+
187 | 'commerce',
|
|
135
|
+
188 | );
|
|
136
|
+
> 189 | await expect(page).toHaveURL(/\/commerce\/commerce\/member\/address(?:\?|$)/);
|
|
137
|
+
| ^ Error: expect(page).toHaveURL(expected) failed
|
|
138
|
+
190 | await expect(page.getByRole('heading', { name: 'Addresses' })).toBeVisible();
|
|
139
|
+
191 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
140
|
+
192 |
|
|
141
|
+
193 | await fillAddressForm(page, fixture);
|
|
142
|
+
194 | const createResponse = waitForAddressResponse(page, 'POST', `${addressActionPath}/createMine`);
|
|
143
|
+
195 | const mineResponse = waitForAddressMine(page);
|
|
144
|
+
196 | await page.getByRole('button', { name: 'Save address', exact: true }).click();
|
|
145
|
+
197 | await createResponse;
|
|
146
|
+
198 | await mineResponse;
|
|
147
|
+
199 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toBeVisible();
|
|
148
|
+
200 |
|
|
149
|
+
201 | return { context, fixture, page, pageErrors };
|
|
150
|
+
202 | }
|
|
151
|
+
203 |
|
|
152
|
+
204 | async function deleteAddressThroughCustomerPage(page: Page, fixture: IAddressFixture) {
|
|
153
|
+
205 | const card = page
|
|
154
|
+
206 | .locator('article')
|
|
155
|
+
207 | .filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
156
|
+
208 | const deleteResponse = waitForAddressResponse(page, 'DELETE', /\/deleteMine\/[^/]+$/);
|
|
157
|
+
209 | const mineResponse = waitForAddressMine(page);
|
|
158
|
+
210 | await card.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
159
|
+
211 | await deleteResponse;
|
|
160
|
+
212 | await mineResponse;
|
|
161
|
+
213 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toHaveCount(0);
|
|
162
|
+
214 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
163
|
+
215 | }
|
|
164
|
+
216 |
|
|
165
|
+
217 | test(
|
|
166
|
+
218 | 'ATP-SSR-01: anonymous Commerce HTML is public and hydration completes',
|
|
167
|
+
219 | { tag: ['@web', '@smoke'] },
|
|
168
|
+
220 | async ({ page, request }) => {
|
|
169
|
+
221 | const response = await request.get('/commerce');
|
|
170
|
+
222 | expect(response.ok()).toBeTruthy();
|
|
171
|
+
223 | expect(response.headers()['cache-control']).toBe('no-cache, no-store, must-revalidate');
|
|
172
|
+
224 | const html = await response.text();
|
|
173
|
+
225 | const normalizedHtml = html.toLowerCase();
|
|
174
|
+
226 | expect(normalizedHtml).not.toContain('data-zova-hydrated');
|
|
175
|
+
227 |
|
|
176
|
+
228 | for (const marker of privateMarkers) {
|
|
177
|
+
229 | expect(normalizedHtml).not.toContain(`"${marker}"`);
|
|
178
|
+
230 | }
|
|
179
|
+
231 |
|
|
180
|
+
232 | const pageErrors = collectPageErrors(page);
|
|
181
|
+
233 | const documentResponse = await page.goto('/commerce', { waitUntil: 'load' });
|
|
182
|
+
234 | expect(documentResponse?.ok()).toBeTruthy();
|
|
183
|
+
235 | await expect(page).toHaveURL(/\/commerce(?:\/|$)/);
|
|
184
|
+
236 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
185
|
+
237 | await expect(page.getByRole('heading', { name: 'Commerce catalogue' })).toBeVisible();
|
|
186
|
+
238 | await expect(page.getByText('Dashboard')).toHaveCount(0);
|
|
187
|
+
239 | await expect(page.locator('body')).toBeVisible();
|
|
188
|
+
240 | await expect(page).not.toHaveTitle(/error/i);
|
|
189
|
+
241 | expect(pageErrors).toEqual([]);
|
|
190
|
+
242 | },
|
|
191
|
+
243 | );
|
|
192
|
+
244 |
|
|
193
|
+
245 | test(
|
|
194
|
+
246 | 'Commerce theme: public local preference mirrors session cookie without hydration mismatch',
|
|
195
|
+
247 | { tag: ['@web', '@ssr', '@theme'] },
|
|
196
|
+
248 | async ({ browser, request }, testInfo) => {
|
|
197
|
+
249 | const customer = await registerCustomer(request, testInfo);
|
|
198
|
+
250 | const context = await browser.newContext();
|
|
199
|
+
251 | await context.addInitScript(() => localStorage.setItem('themedark', 'true'));
|
|
200
|
+
252 | const page = await context.newPage();
|
|
201
|
+
253 | const pageErrors = collectPageErrors(page);
|
|
202
|
+
254 | const consoleErrors: string[] = [];
|
|
203
|
+
255 | page.on('console', message => {
|
|
204
|
+
256 | if (message.type() === 'error' || /hydration mismatch/i.test(message.text())) {
|
|
205
|
+
257 | consoleErrors.push(message.text());
|
|
206
|
+
258 | }
|
|
207
|
+
259 | });
|
|
208
|
+
260 | try {
|
|
209
|
+
261 | await page.goto('/commerce', { waitUntil: 'load' });
|
|
210
|
+
262 | await page.evaluate(() => localStorage.setItem('themedark', 'true'));
|
|
211
|
+
263 | await page.reload({ waitUntil: 'load' });
|
|
212
|
+
264 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
213
|
+
265 | await expect(page.locator('body')).toHaveAttribute('data-theme', 'dark');
|
|
214
|
+
266 | expect((await context.cookies()).find(cookie => cookie.name === 'themedark')?.value).toBe(
|
|
215
|
+
267 | 'true',
|
|
216
|
+
268 | );
|
|
217
|
+
269 |
|
|
218
|
+
270 | await login(
|
|
219
|
+
271 | page,
|
|
220
|
+
272 | '/commerce/commerce/member/address',
|
|
221
|
+
273 | customer.username,
|
|
222
|
+
274 | customer.password,
|
|
223
|
+
275 | 'commerce',
|
|
224
|
+
276 | );
|
|
225
|
+
277 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
226
|
+
278 | await expect(page.locator('body')).toHaveAttribute('data-theme', 'dark');
|
|
227
|
+
279 | expect(pageErrors).toEqual([]);
|
|
228
|
+
280 | expect(consoleErrors).toEqual([]);
|
|
229
|
+
281 | } finally {
|
|
230
|
+
282 | await context.close();
|
|
231
|
+
283 | }
|
|
232
|
+
284 | },
|
|
233
|
+
285 | );
|
|
234
|
+
286 |
|
|
235
|
+
287 | test(
|
|
236
|
+
288 | 'Commerce theme: session cookie mirrors public local preference without hydration mismatch',
|
|
237
|
+
289 | { tag: ['@web', '@ssr', '@theme'] },
|
|
238
|
+
```
|