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.
Files changed (41) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/package.json +1 -1
  4. package/repo-docs/.vitepress/config.mjs +11 -0
  5. package/repo-docs/backend/department-management.md +98 -0
  6. package/repo-docs/backend/menu-authorization.md +124 -0
  7. package/repo-docs/backend/rbac-authorization.md +117 -0
  8. package/repo-docs/backend/resource-field-update.md +2 -2
  9. package/repo-docs/backend/role-management.md +92 -0
  10. package/repo-docs/backend/shared-rbac-architecture.md +339 -0
  11. package/repo-docs/backend/user-management.md +92 -0
  12. package/repo-docs/frontend/model-resource-owner-pattern.md +2 -0
  13. package/repo-docs/frontend/permission-formscene-action-visibility-guide.md +5 -3
  14. package/repo-docs/frontend/table-action-visibility-permission-flow-guide.md +2 -0
  15. package/repo-docs/frontend/table-guide.md +75 -22
  16. package/repo-docs/frontend/table-resource-crud-cookbook.md +25 -4
  17. package/repo-docs/frontend/zova-table-source-reading-map.md +54 -19
  18. package/repo-docs/frontend/zova-table-under-the-hood.md +70 -39
  19. package/test-results/.last-run.json +20 -2
  20. package/test-results/a-commerce-ATP-ADDR-01-aut-11dea-ss-through-Web-self-service/error-context.md +238 -0
  21. package/test-results/a-commerce-ATP-SPC-01-Coup-ef380-mantic-Admin-field-controls/error-context.md +240 -0
  22. package/test-results/a-commerce-ATP-SPC-02-Cate-9f120-on-and-publication-controls/error-context.md +238 -0
  23. package/test-results/a-commerce-ATP-SPC-02-Prod-5f92a-on-and-publication-controls/error-context.md +238 -0
  24. package/test-results/a-commerce-ATP-SPC-02-SKU--2e61e-ency-and-lifecycle-controls/error-context.md +238 -0
  25. package/test-results/a-commerce-ATP-SPC-04-Stoc-fa895--readonly-and-mutation-free/error-context.md +235 -0
  26. package/test-results/a-commerce-ATP-SPC-05-syst-fb406-e-without-mutation-controls/error-context.md +238 -0
  27. package/test-results/a-commerce-Commerce-sessio-4e963-ie-selects-raw-server-theme/error-context.md +226 -0
  28. package/test-results/a-commerce-Commerce-theme--062fe--without-hydration-mismatch/error-context.md +229 -0
  29. package/test-results/a-commerce-PayPal-browser--8672c-t-or-open-an-awaiting-order/error-context.md +238 -0
  30. package/test-results/a-commerce-Payment-callbac-f56c2--reconciles-after-hydration/error-context.md +238 -0
  31. package/test-results/a-commerce-Payment-cancell-3fc74-ified-provider-confirmation/error-context.md +238 -0
  32. package/test-results/a-commerce-Phase-50-60-aut-71d61--observes-operator-shipment/error-context.md +238 -0
  33. package/test-results/a-commerce-Phase-60-custom-407cd-ecutes-a-whole-order-refund/error-context.md +238 -0
  34. package/test-results/cabloy-basic-ATP-BASIC-TAB-e7253-and-pins-configured-columns/error-context.md +240 -0
  35. package/test-results/markdown-ATP-SPC-02-Produc-9d5c2-link-toolbar-edits-Markdown/error-context.md +211 -0
  36. package/test-results/markdown-ATP-SPC-02-Produc-9d6ea-t-editor-and-saves-Markdown/error-context.md +238 -0
  37. package/vona/packages-cli/cli/package.json +1 -1
  38. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
  39. package/vona/packages-cli/cli-set-api/package.json +1 -1
  40. package/vona/pnpm-lock.yaml +97 -11
  41. package/zova/pnpm-lock.yaml +16 -16
@@ -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 >> ATP-SPC-02: SKU renders semantic Admin currency and lifecycle controls
10
+ - Location: repo-e2e/specs/a-commerce.spec.ts:1731:1
11
+
12
+ # Error details
13
+
14
+ ```
15
+ Error: expect(locator).toBeVisible() failed
16
+
17
+ Locator: getByRole('group', { name: 'Product' })
18
+ Expected: visible
19
+ Timeout: 5000ms
20
+ Error: element(s) not found
21
+
22
+ Call log:
23
+ - Expect "toBeVisible" with timeout 5000ms
24
+ - waiting for getByRole('group', { name: 'Product' })
25
+
26
+ ```
27
+
28
+ ```yaml
29
+ - text: 403 Access denied.
30
+ - button "Go Home"
31
+ ```
32
+
33
+ # Test source
34
+
35
+ ```ts
36
+ 1680 | .getByRole('group', { name: 'Category' })
37
+ 1681 | .getByRole('combobox');
38
+ 1682 | await expect(categoryPicker).toBeVisible();
39
+ 1683 | await categoryPicker.selectOption({ label: categoryName });
40
+ 1684 | await expect(categoryPicker).toHaveValue(String(categoryId));
41
+ 1685 | const publication = adminPage.getByRole('group', { name: 'Published' }).getByRole('combobox');
42
+ 1686 | await expect(publication).toBeVisible();
43
+ 1687 | await publication.selectOption({ label: 'Unpublished' });
44
+ 1688 | await expect(publication).toHaveValue('false');
45
+ 1689 |
46
+ 1690 | const productResponse = await adminPage.request.post(productActionPath, {
47
+ 1691 | data: { categoryId, title: productTitle, published: false },
48
+ 1692 | headers,
49
+ 1693 | });
50
+ 1694 | expect(productResponse.ok()).toBeTruthy();
51
+ 1695 | productId = (await productResponse.json()).data;
52
+ 1696 | expectTableIdentity(productId);
53
+ 1697 |
54
+ 1698 | await adminPage.goto(productListPath, { waitUntil: 'load' });
55
+ 1699 | const productRow = adminPage.getByRole('row', { name: new RegExp(productTitle) });
56
+ 1700 | await expect(productRow).toBeVisible();
57
+ 1701 | await expect(productRow.getByText(categoryName, { exact: true })).toBeVisible();
58
+ 1702 | await expect(productRow.getByText('Unpublished', { exact: true })).toBeVisible();
59
+ 1703 |
60
+ 1704 | await adminPage.goto(`${productListPath}/${productId}`, { waitUntil: 'load' });
61
+ 1705 | await expect(
62
+ 1706 | adminPage.getByRole('group', { name: 'Category' }).getByRole('textbox'),
63
+ 1707 | ).toHaveValue(categoryName);
64
+ 1708 | await expect(
65
+ 1709 | adminPage.getByRole('group', { name: 'Published' }).getByRole('textbox'),
66
+ 1710 | ).toHaveValue('Unpublished');
67
+ 1711 | await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
68
+ 1712 | expect(adminPageErrors).toEqual([]);
69
+ 1713 | } finally {
70
+ 1714 | if (productId && headers) {
71
+ 1715 | const response = await adminPage.request.delete(`${productActionPath}/${productId}`, {
72
+ 1716 | headers,
73
+ 1717 | });
74
+ 1718 | expect(response.ok()).toBeTruthy();
75
+ 1719 | }
76
+ 1720 | if (categoryId && headers) {
77
+ 1721 | const response = await adminPage.request.delete(`${categoryActionPath}/${categoryId}`, {
78
+ 1722 | headers,
79
+ 1723 | });
80
+ 1724 | expect(response.ok()).toBeTruthy();
81
+ 1725 | }
82
+ 1726 | await adminContext.close().catch(() => {});
83
+ 1727 | }
84
+ 1728 | },
85
+ 1729 | );
86
+ 1730 |
87
+ 1731 | test(
88
+ 1732 | 'ATP-SPC-02: SKU renders semantic Admin currency and lifecycle controls',
89
+ 1733 | { tag: ['@admin', '@flow', '@sku'] },
90
+ 1734 | async ({ browser }, testInfo) => {
91
+ 1735 | test.setTimeout(60_000);
92
+ 1736 | const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
93
+ 1737 | const categoryName = `E2E SKU Category ${suffix}`;
94
+ 1738 | const productTitle = `E2E SKU Product ${suffix}`;
95
+ 1739 | const skuCode = `E2E-SKU-${suffix}`;
96
+ 1740 | const stockActionPath = '/api/commerce/trade/stockBalance/adjustStock';
97
+ 1741 | const adminContext = await browser.newContext();
98
+ 1742 | const adminPage = await adminContext.newPage();
99
+ 1743 | const adminPageErrors = collectPageErrors(adminPage);
100
+ 1744 | const skuCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Asku/create';
101
+ 1745 | const skuListPath = '/commerce-admin/rest/resource/commerce-catalog%3Asku';
102
+ 1746 | const categoryActionPath = '/api/commerce/catalog/category';
103
+ 1747 | const productActionPath = '/api/commerce/catalog/product';
104
+ 1748 | const skuActionPath = '/api/commerce/catalog/sku';
105
+ 1749 | let categoryId: number | string | undefined;
106
+ 1750 | let productId: number | string | undefined;
107
+ 1751 | let skuId: number | string | undefined;
108
+ 1752 | let headers: { Authorization: string } | undefined;
109
+ 1753 | try {
110
+ 1754 | await adminPage.setViewportSize({ width: 1440, height: 900 });
111
+ 1755 | await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
112
+ 1756 | const accessToken = (await adminContext.cookies()).find(
113
+ 1757 | cookie => cookie.name === 'token',
114
+ 1758 | )?.value;
115
+ 1759 | expect(accessToken).toBeTruthy();
116
+ 1760 | headers = { Authorization: `Bearer ${accessToken}` };
117
+ 1761 |
118
+ 1762 | const categoryResponse = await adminPage.request.post(categoryActionPath, {
119
+ 1763 | data: { name: categoryName, published: true },
120
+ 1764 | headers,
121
+ 1765 | });
122
+ 1766 | expect(categoryResponse.ok()).toBeTruthy();
123
+ 1767 | categoryId = (await categoryResponse.json()).data;
124
+ 1768 | expectTableIdentity(categoryId);
125
+ 1769 |
126
+ 1770 | const productResponse = await adminPage.request.post(productActionPath, {
127
+ 1771 | data: { categoryId, title: productTitle, published: true },
128
+ 1772 | headers,
129
+ 1773 | });
130
+ 1774 | expect(productResponse.ok()).toBeTruthy();
131
+ 1775 | productId = (await productResponse.json()).data;
132
+ 1776 | expectTableIdentity(productId);
133
+ 1777 |
134
+ 1778 | await adminPage.goto(skuCreatePath, { waitUntil: 'load' });
135
+ 1779 | const productField = adminPage.getByRole('group', { name: 'Product' });
136
+ > 1780 | await expect(productField).toBeVisible();
137
+ | ^ Error: expect(locator).toBeVisible() failed
138
+ 1781 | const productPicker = productField.getByRole('combobox');
139
+ 1782 | await expect(productPicker).toBeVisible();
140
+ 1783 | await expect(productPicker).toHaveValue('');
141
+ 1784 | const lifecycle = adminPage
142
+ 1785 | .getByRole('group', { name: 'SKU lifecycle' })
143
+ 1786 | .getByRole('combobox');
144
+ 1787 | await expect(lifecycle).toBeVisible();
145
+ 1788 | await expect(lifecycle.locator('option')).toHaveText([
146
+ 1789 | '',
147
+ 1790 | 'Draft',
148
+ 1791 | 'Active',
149
+ 1792 | 'Inactive',
150
+ 1793 | 'Archived',
151
+ 1794 | ]);
152
+ 1795 | await expect(adminPage.getByRole('group', { name: 'Price (cents)' })).toBeVisible();
153
+ 1796 |
154
+ 1797 | const skuResponse = await adminPage.request.post(skuActionPath, {
155
+ 1798 | data: { code: skuCode, productId, priceCents: 1234, lifecycle: 'draft' },
156
+ 1799 | headers,
157
+ 1800 | });
158
+ 1801 | expect(skuResponse.ok()).toBeTruthy();
159
+ 1802 | skuId = (await skuResponse.json()).data;
160
+ 1803 | expectTableIdentity(skuId);
161
+ 1804 |
162
+ 1805 | await adminPage.goto(skuListPath, { waitUntil: 'load' });
163
+ 1806 | const skuRow = adminPage.getByRole('row', { name: new RegExp(skuCode) });
164
+ 1807 | await expect(skuRow).toBeVisible();
165
+ 1808 | await expect(skuRow.getByText('12.34', { exact: true })).toBeVisible();
166
+ 1809 | await expect(skuRow.getByText('0', { exact: true })).toBeVisible();
167
+ 1810 | await expect(skuRow.getByText('Draft', { exact: true })).toBeVisible();
168
+ 1811 | await expect(skuRow.getByRole('link', { name: skuCode, exact: true })).toBeVisible();
169
+ 1812 | const adjustStockButton = skuRow.getByRole('button', { name: 'Adjust stock', exact: true });
170
+ 1813 | await expect(adjustStockButton).toBeVisible();
171
+ 1814 | await expect(adminPage.locator('html')).toHaveAttribute(
172
+ 1815 | 'data-zova-hydrated',
173
+ 1816 | 'commerceAdmin',
174
+ 1817 | );
175
+ 1818 | await adjustStockButton.click();
176
+ 1819 | await expect(adminPage.getByRole('dialog')).toBeVisible();
177
+ 1820 | await expect(adminPage.getByRole('dialog').getByText(skuCode, { exact: true })).toBeVisible();
178
+ 1821 | await expect(adminPage.getByRole('dialog').getByText('0', { exact: true })).toBeVisible();
179
+ 1822 | const deltaInput = adminPage.getByRole('group', { name: 'Delta' }).getByRole('spinbutton');
180
+ 1823 | const reasonInput = adminPage.getByRole('group', { name: 'Reason' }).getByRole('textbox');
181
+ 1824 | await deltaInput.fill('7');
182
+ 1825 | await reasonInput.fill('Initial E2E stock');
183
+ 1826 | const stockRequest = adminPage.waitForRequest(request => {
184
+ 1827 | return request.method() === 'POST' && new URL(request.url()).pathname === stockActionPath;
185
+ 1828 | });
186
+ 1829 | const stockResponse = adminPage.waitForResponse(response => {
187
+ 1830 | return (
188
+ 1831 | response.request().method() === 'POST' &&
189
+ 1832 | new URL(response.url()).pathname === stockActionPath
190
+ 1833 | );
191
+ 1834 | });
192
+ 1835 | await adminPage
193
+ 1836 | .getByRole('dialog')
194
+ 1837 | .getByRole('button', { name: 'Adjust stock', exact: true })
195
+ 1838 | .click();
196
+ 1839 | const stockRequestValue = await stockRequest;
197
+ 1840 | expect(stockRequestValue.postDataJSON()).toMatchObject({
198
+ 1841 | skuId,
199
+ 1842 | delta: 7,
200
+ 1843 | reason: 'Initial E2E stock',
201
+ 1844 | correlationId: expect.any(String),
202
+ 1845 | });
203
+ 1846 | expect((await stockResponse).ok()).toBeTruthy();
204
+ 1847 | await expect(adminPage.getByRole('dialog')).toHaveCount(0);
205
+ 1848 | await expect(skuRow.getByText('7', { exact: true })).toBeVisible();
206
+ 1849 | await expect(
207
+ 1850 | adminPage.locator('section').getByText('SKU code', { exact: true }),
208
+ 1851 | ).toBeVisible();
209
+ 1852 | await expect(adminPage.getByText('Created At', { exact: true })).toHaveCount(1);
210
+ 1853 | await expect(adminPage.getByRole('group', { name: 'Attributes' })).toHaveCount(0);
211
+ 1854 |
212
+ 1855 | await adminPage.goto(`${skuListPath}/${skuId}`, { waitUntil: 'load' });
213
+ 1856 | await expect(
214
+ 1857 | adminPage.getByRole('group', { name: 'SKU lifecycle' }).getByRole('textbox'),
215
+ 1858 | ).toHaveValue('Draft');
216
+ 1859 | await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
217
+ 1860 | await expect(adminPage.getByRole('button', { name: 'Back', exact: true })).toBeVisible();
218
+ 1861 | expect(adminPageErrors).toEqual([]);
219
+ 1862 | } finally {
220
+ 1863 | if (skuId && headers) {
221
+ 1864 | await adminPage.request.delete(`${stockActionPath}/${skuId}`, { headers }).catch(() => {});
222
+ 1865 | const response = await adminPage.request.delete(`${skuActionPath}/${skuId}`, { headers });
223
+ 1866 | expect(response.ok()).toBeTruthy();
224
+ 1867 | }
225
+ 1868 | if (productId && headers) {
226
+ 1869 | const response = await adminPage.request.delete(`${productActionPath}/${productId}`, {
227
+ 1870 | headers,
228
+ 1871 | });
229
+ 1872 | expect(response.ok()).toBeTruthy();
230
+ 1873 | }
231
+ 1874 | if (categoryId && headers) {
232
+ 1875 | const response = await adminPage.request.delete(`${categoryActionPath}/${categoryId}`, {
233
+ 1876 | headers,
234
+ 1877 | });
235
+ 1878 | expect(response.ok()).toBeTruthy();
236
+ 1879 | }
237
+ 1880 | await adminContext.close().catch(() => {});
238
+ ```
@@ -0,0 +1,235 @@
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 >> ATP-SPC-04: Stock Admin resources remain readonly and mutation-free
10
+ - Location: repo-e2e/specs/a-commerce.spec.ts:1885:1
11
+
12
+ # Error details
13
+
14
+ ```
15
+ Error: expect(page).toHaveURL(expected) failed
16
+
17
+ Expected pattern: /\/commerce-admin\/rest\/resource\/commerce-trade(?:%3A|:|%253A)stockBalance(?:[/?#]|$)/
18
+ Received string: "http://127.0.0.1:7102/commerce-admin/home/base/errorAccessDenied"
19
+ Timeout: 5000ms
20
+
21
+ Call log:
22
+ - Expect "toHaveURL" with timeout 5000ms
23
+ 2 × locator resolved to <html lang="en-us">…</html>
24
+ - unexpected value "http://127.0.0.1:7102/commerce-admin/home/base/errorAccessDenied"
25
+ 12 × locator resolved to <html lang="en-us" data-zova-hydrated="commerceAdmin">…</html>
26
+ - unexpected value "http://127.0.0.1:7102/commerce-admin/home/base/errorAccessDenied"
27
+
28
+ ```
29
+
30
+ ```yaml
31
+ - text: 403 Access denied.
32
+ - button "Go Home"
33
+ ```
34
+
35
+ # Test source
36
+
37
+ ```ts
38
+ 1815 | 'data-zova-hydrated',
39
+ 1816 | 'commerceAdmin',
40
+ 1817 | );
41
+ 1818 | await adjustStockButton.click();
42
+ 1819 | await expect(adminPage.getByRole('dialog')).toBeVisible();
43
+ 1820 | await expect(adminPage.getByRole('dialog').getByText(skuCode, { exact: true })).toBeVisible();
44
+ 1821 | await expect(adminPage.getByRole('dialog').getByText('0', { exact: true })).toBeVisible();
45
+ 1822 | const deltaInput = adminPage.getByRole('group', { name: 'Delta' }).getByRole('spinbutton');
46
+ 1823 | const reasonInput = adminPage.getByRole('group', { name: 'Reason' }).getByRole('textbox');
47
+ 1824 | await deltaInput.fill('7');
48
+ 1825 | await reasonInput.fill('Initial E2E stock');
49
+ 1826 | const stockRequest = adminPage.waitForRequest(request => {
50
+ 1827 | return request.method() === 'POST' && new URL(request.url()).pathname === stockActionPath;
51
+ 1828 | });
52
+ 1829 | const stockResponse = adminPage.waitForResponse(response => {
53
+ 1830 | return (
54
+ 1831 | response.request().method() === 'POST' &&
55
+ 1832 | new URL(response.url()).pathname === stockActionPath
56
+ 1833 | );
57
+ 1834 | });
58
+ 1835 | await adminPage
59
+ 1836 | .getByRole('dialog')
60
+ 1837 | .getByRole('button', { name: 'Adjust stock', exact: true })
61
+ 1838 | .click();
62
+ 1839 | const stockRequestValue = await stockRequest;
63
+ 1840 | expect(stockRequestValue.postDataJSON()).toMatchObject({
64
+ 1841 | skuId,
65
+ 1842 | delta: 7,
66
+ 1843 | reason: 'Initial E2E stock',
67
+ 1844 | correlationId: expect.any(String),
68
+ 1845 | });
69
+ 1846 | expect((await stockResponse).ok()).toBeTruthy();
70
+ 1847 | await expect(adminPage.getByRole('dialog')).toHaveCount(0);
71
+ 1848 | await expect(skuRow.getByText('7', { exact: true })).toBeVisible();
72
+ 1849 | await expect(
73
+ 1850 | adminPage.locator('section').getByText('SKU code', { exact: true }),
74
+ 1851 | ).toBeVisible();
75
+ 1852 | await expect(adminPage.getByText('Created At', { exact: true })).toHaveCount(1);
76
+ 1853 | await expect(adminPage.getByRole('group', { name: 'Attributes' })).toHaveCount(0);
77
+ 1854 |
78
+ 1855 | await adminPage.goto(`${skuListPath}/${skuId}`, { waitUntil: 'load' });
79
+ 1856 | await expect(
80
+ 1857 | adminPage.getByRole('group', { name: 'SKU lifecycle' }).getByRole('textbox'),
81
+ 1858 | ).toHaveValue('Draft');
82
+ 1859 | await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
83
+ 1860 | await expect(adminPage.getByRole('button', { name: 'Back', exact: true })).toBeVisible();
84
+ 1861 | expect(adminPageErrors).toEqual([]);
85
+ 1862 | } finally {
86
+ 1863 | if (skuId && headers) {
87
+ 1864 | await adminPage.request.delete(`${stockActionPath}/${skuId}`, { headers }).catch(() => {});
88
+ 1865 | const response = await adminPage.request.delete(`${skuActionPath}/${skuId}`, { headers });
89
+ 1866 | expect(response.ok()).toBeTruthy();
90
+ 1867 | }
91
+ 1868 | if (productId && headers) {
92
+ 1869 | const response = await adminPage.request.delete(`${productActionPath}/${productId}`, {
93
+ 1870 | headers,
94
+ 1871 | });
95
+ 1872 | expect(response.ok()).toBeTruthy();
96
+ 1873 | }
97
+ 1874 | if (categoryId && headers) {
98
+ 1875 | const response = await adminPage.request.delete(`${categoryActionPath}/${categoryId}`, {
99
+ 1876 | headers,
100
+ 1877 | });
101
+ 1878 | expect(response.ok()).toBeTruthy();
102
+ 1879 | }
103
+ 1880 | await adminContext.close().catch(() => {});
104
+ 1881 | }
105
+ 1882 | },
106
+ 1883 | );
107
+ 1884 |
108
+ 1885 | test(
109
+ 1886 | 'ATP-SPC-04: Stock Admin resources remain readonly and mutation-free',
110
+ 1887 | { tag: ['@admin', '@flow', '@stock'] },
111
+ 1888 | async ({ browser }) => {
112
+ 1889 | test.setTimeout(60_000);
113
+ 1890 | const adminContext = await browser.newContext();
114
+ 1891 | const adminPage = await adminContext.newPage();
115
+ 1892 | const adminPageErrors = collectPageErrors(adminPage);
116
+ 1893 | const mutationMethods: string[] = [];
117
+ 1894 | adminPage.on('request', request => {
118
+ 1895 | const url = new URL(request.url());
119
+ 1896 | if (
120
+ 1897 | url.pathname.includes('/api/commerce/trade/stock') &&
121
+ 1898 | ['POST', 'PATCH', 'PUT', 'DELETE'].includes(request.method())
122
+ 1899 | ) {
123
+ 1900 | mutationMethods.push(request.method());
124
+ 1901 | }
125
+ 1902 | });
126
+ 1903 |
127
+ 1904 | try {
128
+ 1905 | await adminPage.setViewportSize({ width: 1440, height: 900 });
129
+ 1906 | await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
130
+ 1907 |
131
+ 1908 | for (const [resourcePath, title] of [
132
+ 1909 | ['commerce-trade%3AstockBalance', 'Stock Balance'],
133
+ 1910 | ['commerce-trade%3AstockAudit', 'Stock Audit'],
134
+ 1911 | ] as const) {
135
+ 1912 | await adminPage.goto(`/commerce-admin/rest/resource/${resourcePath}`, {
136
+ 1913 | waitUntil: 'load',
137
+ 1914 | });
138
+ > 1915 | await expect(adminPage).toHaveURL(
139
+ | ^ Error: expect(page).toHaveURL(expected) failed
140
+ 1916 | new RegExp(
141
+ 1917 | `/commerce-admin/rest/resource/commerce-trade(?:%3A|:|%253A)${title === 'Stock Balance' ? 'stockBalance' : 'stockAudit'}(?:[/?#]|$)`,
142
+ 1918 | ),
143
+ 1919 | );
144
+ 1920 | await expect(adminPage.getByText(title, { exact: true }).first()).toBeVisible();
145
+ 1921 | await expect(adminPage.getByRole('button', { name: 'Create', exact: true })).toHaveCount(0);
146
+ 1922 | await expect(
147
+ 1923 | adminPage.locator('a[href*="/rest/resource/"]').filter({ hasText: 'Create' }),
148
+ 1924 | ).toHaveCount(0);
149
+ 1925 | await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
150
+ 1926 | }
151
+ 1927 |
152
+ 1928 | expect(mutationMethods).toEqual([]);
153
+ 1929 | expect(adminPageErrors).toEqual([]);
154
+ 1930 | } finally {
155
+ 1931 | await adminContext.close().catch(() => {});
156
+ 1932 | }
157
+ 1933 | },
158
+ 1934 | );
159
+ 1935 |
160
+ 1936 | test(
161
+ 1937 | 'Commerce Cart: anonymous browser is redirected to login',
162
+ 1938 | { tag: ['@web', '@cart'] },
163
+ 1939 | async ({ page, request }) => {
164
+ 1940 | const path = '/commerce/commerce/trade/cart';
165
+ 1941 | const response = await request.get(path);
166
+ 1942 | expect(response.ok()).toBeTruthy();
167
+ 1943 | expect((await response.text()).toLowerCase()).not.toContain('data-zova-hydrated');
168
+ 1944 |
169
+ 1945 | const pageErrors = collectPageErrors(page);
170
+ 1946 | const documentResponse = await page.goto(path, { waitUntil: 'load' });
171
+ 1947 | expect(documentResponse?.ok()).toBeTruthy();
172
+ 1948 | await expect(page).toHaveURL(/\/commerce\/login\?(?:.*&)?returnTo=/);
173
+ 1949 | expect(pageErrors).toEqual([]);
174
+ 1950 | },
175
+ 1951 | );
176
+ 1952 |
177
+ 1953 | test(
178
+ 1954 | 'Phase 50: anonymous payment and order routes remain SSR-neutral and protected',
179
+ 1955 | { tag: ['@web', '@flow'] },
180
+ 1956 | async ({ page, request }) => {
181
+ 1957 | const routes = [
182
+ 1958 | ['/commerce/commerce/trade/checkout', '/commerce/trade/checkout'],
183
+ 1959 | ['/commerce/commerce/trade/payment/1/1', '/commerce/trade/payment/1/1'],
184
+ 1960 | ['/commerce/commerce/trade/orders', '/commerce/trade/orders'],
185
+ 1961 | ['/commerce/commerce/trade/order/1', '/commerce/trade/order/1'],
186
+ 1962 | ] as const;
187
+ 1963 | for (const [path, routePath] of routes) {
188
+ 1964 | const response = await request.get(path, { maxRedirects: 0 });
189
+ 1965 | expect(response.status(), path).toBe(302);
190
+ 1966 | expect(response.headers()['cache-control'], path).toBe('private, no-store');
191
+ 1967 | expect(response.headers().location, path).toMatch(/^\/commerce\/login\?(?:.*&)?returnTo=/);
192
+ 1968 | const html = await response.text();
193
+ 1969 | expect(html.toLowerCase(), path).not.toContain('data-zova-hydrated');
194
+ 1970 | expect(html, path).not.toContain('Payment Customer');
195
+ 1971 | expect(html, path).not.toContain('Payment succeeded');
196
+ 1972 | expect(html, path).not.toContain('Order #');
197
+ 1973 | expect(html, path).not.toContain('Checkout');
198
+ 1974 | expect(html, path).not.toContain('Mock payment simulator');
199
+ 1975 | expect(html, path).not.toContain('My orders');
200
+ 1976 |
201
+ 1977 | const pageErrors = collectPageErrors(page);
202
+ 1978 | const documentResponse = await page.goto(path, { waitUntil: 'load' });
203
+ 1979 | expect(documentResponse?.ok(), path).toBeTruthy();
204
+ 1980 | await expect(page, path).toHaveURL(/\/commerce\/login\?(?:.*&)?returnTo=/);
205
+ 1981 | const loginUrl = new URL(page.url());
206
+ 1982 | expect(loginUrl.searchParams.getAll('returnTo'), path).toEqual([routePath]);
207
+ 1983 | expect(pageErrors, path).toEqual([]);
208
+ 1984 | }
209
+ 1985 | },
210
+ 1986 | );
211
+ 1987 |
212
+ 1988 | test(
213
+ 1989 | 'ATP-SSR-02: Commerce Admin is an independent SSR site',
214
+ 1990 | { tag: ['@admin', '@smoke'] },
215
+ 1991 | async ({ page, request }) => {
216
+ 1992 | const response = await request.get('/commerce-admin/', { maxRedirects: 0 });
217
+ 1993 | expect(response.status()).toBe(302);
218
+ 1994 | const loginPath = response.headers().location;
219
+ 1995 | expect(loginPath).toMatch(/^\/commerce-admin\/login(?:\?|$)/);
220
+ 1996 |
221
+ 1997 | const loginResponse = await request.get(loginPath!);
222
+ 1998 | expect(loginResponse.ok()).toBeTruthy();
223
+ 1999 | expect((await loginResponse.text()).toLowerCase()).not.toContain('data-zova-hydrated');
224
+ 2000 |
225
+ 2001 | const pageErrors = collectPageErrors(page);
226
+ 2002 | const documentResponse = await page.goto('/commerce-admin/', { waitUntil: 'load' });
227
+ 2003 | expect(documentResponse?.ok()).toBeTruthy();
228
+ 2004 | await expect(page).toHaveURL(/\/commerce-admin\/login(?:\?|$)/);
229
+ 2005 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerceAdmin');
230
+ 2006 | await expect(page.locator('body')).toBeVisible();
231
+ 2007 | expect(pageErrors).toEqual([]);
232
+ 2008 | },
233
+ 2009 | );
234
+ 2010 |
235
+ ```