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: Category renders semantic Admin relation and publication controls
10
+ - Location: repo-e2e/specs/a-commerce.spec.ts:1532:1
11
+
12
+ # Error details
13
+
14
+ ```
15
+ Error: expect(locator).toBeVisible() failed
16
+
17
+ Locator: getByRole('group', { name: 'Published' }).getByRole('combobox')
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: 'Published' }).getByRole('combobox')
25
+
26
+ ```
27
+
28
+ ```yaml
29
+ - text: 403 Access denied.
30
+ - button "Go Home"
31
+ ```
32
+
33
+ # Test source
34
+
35
+ ```ts
36
+ 1470 | await expect(adminPage.getByRole('group', { name: groupName })).toBeVisible();
37
+ 1471 | }
38
+ 1472 |
39
+ 1473 | const state = adminPage
40
+ 1474 | .getByRole('group', { name: 'Template State *' })
41
+ 1475 | .getByRole('combobox');
42
+ 1476 | await expect(state).toBeVisible();
43
+ 1477 | await state.selectOption({ label: 'Active' });
44
+ 1478 | await expect(state).toHaveValue('active');
45
+ 1479 |
46
+ 1480 | const discountInput = adminPage
47
+ 1481 | .getByRole('group', { name: 'Fixed Discount *' })
48
+ 1482 | .getByRole('textbox');
49
+ 1483 | const minSpendInput = adminPage
50
+ 1484 | .getByRole('group', { name: 'Minimum Spend *' })
51
+ 1485 | .getByRole('textbox');
52
+ 1486 | await discountInput.fill('12.34');
53
+ 1487 | await minSpendInput.fill('45.67');
54
+ 1488 | await expect(discountInput).toHaveValue('12.34');
55
+ 1489 | await expect(minSpendInput).toHaveValue('45.67');
56
+ 1490 | await expect(
57
+ 1491 | adminPage.getByRole('group', { name: 'Valid From *' }).locator('input[type="date"]'),
58
+ 1492 | ).toBeVisible();
59
+ 1493 | await expect(
60
+ 1494 | adminPage.getByRole('group', { name: 'Valid Until *' }).locator('input[type="date"]'),
61
+ 1495 | ).toBeVisible();
62
+ 1496 |
63
+ 1497 | await adminPage
64
+ 1498 | .getByRole('group', { name: 'Name *' })
65
+ 1499 | .getByRole('textbox')
66
+ 1500 | .fill('E2E Coupon Template');
67
+ 1501 | await adminPage.getByRole('group', { name: 'Currency *' }).getByRole('textbox').fill('USD');
68
+ 1502 | await adminPage
69
+ 1503 | .getByRole('group', { name: 'Valid From *' })
70
+ 1504 | .getByRole('textbox')
71
+ 1505 | .fill('2026-08-04');
72
+ 1506 | await adminPage
73
+ 1507 | .getByRole('group', { name: 'Valid Until *' })
74
+ 1508 | .getByRole('textbox')
75
+ 1509 | .fill('2026-12-31');
76
+ 1510 | await adminPage
77
+ 1511 | .getByRole('group', { name: 'Total Issue Limit' })
78
+ 1512 | .getByRole('textbox')
79
+ 1513 | .fill('10');
80
+ 1514 | await adminPage
81
+ 1515 | .getByRole('group', { name: 'Total Usage Limit' })
82
+ 1516 | .getByRole('textbox')
83
+ 1517 | .fill('10');
84
+ 1518 | await adminPage
85
+ 1519 | .getByRole('group', { name: 'Per-customer Issue Limit' })
86
+ 1520 | .getByRole('textbox')
87
+ 1521 | .fill('1');
88
+ 1522 |
89
+ 1523 | await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toBeVisible();
90
+ 1524 | await expect(adminPage.getByRole('button', { name: 'Back', exact: true })).toBeVisible();
91
+ 1525 | expect(adminPageErrors).toEqual([]);
92
+ 1526 | } finally {
93
+ 1527 | await adminContext.close().catch(() => {});
94
+ 1528 | }
95
+ 1529 | },
96
+ 1530 | );
97
+ 1531 |
98
+ 1532 | test(
99
+ 1533 | 'ATP-SPC-02: Category renders semantic Admin relation and publication controls',
100
+ 1534 | { tag: ['@admin', '@flow', '@category'] },
101
+ 1535 | async ({ browser }, testInfo) => {
102
+ 1536 | test.setTimeout(60_000);
103
+ 1537 | const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
104
+ 1538 | const parentName = `E2E Category Parent ${suffix}`;
105
+ 1539 | const childName = `E2E Category Child ${suffix}`;
106
+ 1540 | const adminContext = await browser.newContext();
107
+ 1541 | const adminPage = await adminContext.newPage();
108
+ 1542 | const adminPageErrors = collectPageErrors(adminPage);
109
+ 1543 | const categoryCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Acategory/create';
110
+ 1544 | const categoryListPath = '/commerce-admin/rest/resource/commerce-catalog%3Acategory';
111
+ 1545 | const categoryActionPath = '/api/commerce/catalog/category';
112
+ 1546 | let parentId: number | string | undefined;
113
+ 1547 | let childId: number | string | undefined;
114
+ 1548 | let headers: { Authorization: string } | undefined;
115
+ 1549 | try {
116
+ 1550 | await adminPage.setViewportSize({ width: 1440, height: 900 });
117
+ 1551 | await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
118
+ 1552 | const accessToken = (await adminContext.cookies()).find(
119
+ 1553 | cookie => cookie.name === 'token',
120
+ 1554 | )?.value;
121
+ 1555 | expect(accessToken).toBeTruthy();
122
+ 1556 | headers = { Authorization: `Bearer ${accessToken}` };
123
+ 1557 |
124
+ 1558 | const parentCreateResponse = await adminPage.request.post(categoryActionPath, {
125
+ 1559 | data: { name: parentName, published: false },
126
+ 1560 | headers,
127
+ 1561 | });
128
+ 1562 | expect(parentCreateResponse.ok()).toBeTruthy();
129
+ 1563 | parentId = (await parentCreateResponse.json()).data;
130
+ 1564 | expectTableIdentity(parentId);
131
+ 1565 |
132
+ 1566 | await adminPage.goto(categoryCreatePath, { waitUntil: 'load' });
133
+ 1567 | const parentPublication = adminPage
134
+ 1568 | .getByRole('group', { name: 'Published' })
135
+ 1569 | .getByRole('combobox');
136
+ > 1570 | await expect(parentPublication).toBeVisible();
137
+ | ^ Error: expect(locator).toBeVisible() failed
138
+ 1571 | await expect(parentPublication.locator('option')).toHaveText([
139
+ 1572 | '',
140
+ 1573 | 'Unpublished',
141
+ 1574 | 'Published',
142
+ 1575 | ]);
143
+ 1576 | await parentPublication.selectOption({ label: 'Unpublished' });
144
+ 1577 | await expect(parentPublication).toHaveValue('false');
145
+ 1578 |
146
+ 1579 | const parentPicker = adminPage
147
+ 1580 | .getByRole('group', { name: 'Parent category' })
148
+ 1581 | .getByRole('combobox');
149
+ 1582 | await expect(parentPicker).toBeVisible();
150
+ 1583 | await parentPicker.selectOption({ label: parentName });
151
+ 1584 | const parentValue = await parentPicker.inputValue();
152
+ 1585 | expect(parentValue).toBe(String(parentId));
153
+ 1586 | const childPublication = adminPage
154
+ 1587 | .getByRole('group', { name: 'Published' })
155
+ 1588 | .getByRole('combobox');
156
+ 1589 | await childPublication.selectOption({ label: 'Published' });
157
+ 1590 | await expect(childPublication).toHaveValue('true');
158
+ 1591 |
159
+ 1592 | const childCreateResponse = await adminPage.request.post(categoryActionPath, {
160
+ 1593 | data: { name: childName, parentId: parentValue, published: true },
161
+ 1594 | headers,
162
+ 1595 | });
163
+ 1596 | expect(childCreateResponse.ok()).toBeTruthy();
164
+ 1597 | childId = (await childCreateResponse.json()).data;
165
+ 1598 | expectTableIdentity(childId);
166
+ 1599 |
167
+ 1600 | await adminPage.goto(categoryListPath, { waitUntil: 'load' });
168
+ 1601 | const childRow = adminPage.getByRole('row', { name: new RegExp(childName) });
169
+ 1602 | await expect(childRow).toBeVisible();
170
+ 1603 | await expect(childRow.getByText(parentName, { exact: true })).toBeVisible();
171
+ 1604 | await expect(childRow.getByText('Published', { exact: true })).toBeVisible();
172
+ 1605 |
173
+ 1606 | await adminPage.goto(`${categoryListPath}/${parentId}/edit`, { waitUntil: 'load' });
174
+ 1607 | await expect(
175
+ 1608 | adminPage.getByRole('group', { name: 'Published' }).getByRole('combobox'),
176
+ 1609 | ).toHaveValue('false');
177
+ 1610 |
178
+ 1611 | await adminPage.goto(`${categoryListPath}/${childId}/edit`, { waitUntil: 'load' });
179
+ 1612 | await expect(
180
+ 1613 | adminPage.getByRole('group', { name: 'Published' }).getByRole('combobox'),
181
+ 1614 | ).toHaveValue('true');
182
+ 1615 |
183
+ 1616 | await adminPage.goto(`${categoryListPath}/${childId}`, { waitUntil: 'load' });
184
+ 1617 | await expect(
185
+ 1618 | adminPage.getByRole('group', { name: 'Parent category' }).getByRole('textbox'),
186
+ 1619 | ).toHaveValue(parentName);
187
+ 1620 | await expect(
188
+ 1621 | adminPage.getByRole('group', { name: 'Published' }).getByRole('textbox'),
189
+ 1622 | ).toHaveValue('Published');
190
+ 1623 | await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
191
+ 1624 | expect(adminPageErrors).toEqual([]);
192
+ 1625 | } finally {
193
+ 1626 | if (childId && headers) {
194
+ 1627 | const response = await adminPage.request.delete(`${categoryActionPath}/${childId}`, {
195
+ 1628 | headers,
196
+ 1629 | });
197
+ 1630 | expect(response.ok()).toBeTruthy();
198
+ 1631 | }
199
+ 1632 | if (parentId && headers) {
200
+ 1633 | const response = await adminPage.request.delete(`${categoryActionPath}/${parentId}`, {
201
+ 1634 | headers,
202
+ 1635 | });
203
+ 1636 | expect(response.ok()).toBeTruthy();
204
+ 1637 | }
205
+ 1638 | await adminContext.close().catch(() => {});
206
+ 1639 | }
207
+ 1640 | },
208
+ 1641 | );
209
+ 1642 |
210
+ 1643 | test(
211
+ 1644 | 'ATP-SPC-02: Product renders semantic Admin relation and publication controls',
212
+ 1645 | { tag: ['@admin', '@flow', '@product'] },
213
+ 1646 | async ({ browser }, testInfo) => {
214
+ 1647 | test.setTimeout(60_000);
215
+ 1648 | const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
216
+ 1649 | const categoryName = `E2E Product Category ${suffix}`;
217
+ 1650 | const productTitle = `E2E Product ${suffix}`;
218
+ 1651 | const adminContext = await browser.newContext();
219
+ 1652 | const adminPage = await adminContext.newPage();
220
+ 1653 | const adminPageErrors = collectPageErrors(adminPage);
221
+ 1654 | const productCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Aproduct/create';
222
+ 1655 | const productListPath = '/commerce-admin/rest/resource/commerce-catalog%3Aproduct';
223
+ 1656 | const categoryActionPath = '/api/commerce/catalog/category';
224
+ 1657 | const productActionPath = '/api/commerce/catalog/product';
225
+ 1658 | let categoryId: number | string | undefined;
226
+ 1659 | let productId: number | string | undefined;
227
+ 1660 | let headers: { Authorization: string } | undefined;
228
+ 1661 | try {
229
+ 1662 | await adminPage.setViewportSize({ width: 1440, height: 900 });
230
+ 1663 | await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
231
+ 1664 | const accessToken = (await adminContext.cookies()).find(
232
+ 1665 | cookie => cookie.name === 'token',
233
+ 1666 | )?.value;
234
+ 1667 | expect(accessToken).toBeTruthy();
235
+ 1668 | headers = { Authorization: `Bearer ${accessToken}` };
236
+ 1669 |
237
+ 1670 | const categoryResponse = await adminPage.request.post(categoryActionPath, {
238
+ ```
@@ -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: Product renders semantic Admin relation and publication controls
10
+ - Location: repo-e2e/specs/a-commerce.spec.ts:1643:1
11
+
12
+ # Error details
13
+
14
+ ```
15
+ Error: expect(locator).toBeVisible() failed
16
+
17
+ Locator: getByRole('group', { name: 'Category' }).getByRole('combobox')
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: 'Category' }).getByRole('combobox')
25
+
26
+ ```
27
+
28
+ ```yaml
29
+ - text: 403 Access denied.
30
+ - button "Go Home"
31
+ ```
32
+
33
+ # Test source
34
+
35
+ ```ts
36
+ 1582 | await expect(parentPicker).toBeVisible();
37
+ 1583 | await parentPicker.selectOption({ label: parentName });
38
+ 1584 | const parentValue = await parentPicker.inputValue();
39
+ 1585 | expect(parentValue).toBe(String(parentId));
40
+ 1586 | const childPublication = adminPage
41
+ 1587 | .getByRole('group', { name: 'Published' })
42
+ 1588 | .getByRole('combobox');
43
+ 1589 | await childPublication.selectOption({ label: 'Published' });
44
+ 1590 | await expect(childPublication).toHaveValue('true');
45
+ 1591 |
46
+ 1592 | const childCreateResponse = await adminPage.request.post(categoryActionPath, {
47
+ 1593 | data: { name: childName, parentId: parentValue, published: true },
48
+ 1594 | headers,
49
+ 1595 | });
50
+ 1596 | expect(childCreateResponse.ok()).toBeTruthy();
51
+ 1597 | childId = (await childCreateResponse.json()).data;
52
+ 1598 | expectTableIdentity(childId);
53
+ 1599 |
54
+ 1600 | await adminPage.goto(categoryListPath, { waitUntil: 'load' });
55
+ 1601 | const childRow = adminPage.getByRole('row', { name: new RegExp(childName) });
56
+ 1602 | await expect(childRow).toBeVisible();
57
+ 1603 | await expect(childRow.getByText(parentName, { exact: true })).toBeVisible();
58
+ 1604 | await expect(childRow.getByText('Published', { exact: true })).toBeVisible();
59
+ 1605 |
60
+ 1606 | await adminPage.goto(`${categoryListPath}/${parentId}/edit`, { waitUntil: 'load' });
61
+ 1607 | await expect(
62
+ 1608 | adminPage.getByRole('group', { name: 'Published' }).getByRole('combobox'),
63
+ 1609 | ).toHaveValue('false');
64
+ 1610 |
65
+ 1611 | await adminPage.goto(`${categoryListPath}/${childId}/edit`, { waitUntil: 'load' });
66
+ 1612 | await expect(
67
+ 1613 | adminPage.getByRole('group', { name: 'Published' }).getByRole('combobox'),
68
+ 1614 | ).toHaveValue('true');
69
+ 1615 |
70
+ 1616 | await adminPage.goto(`${categoryListPath}/${childId}`, { waitUntil: 'load' });
71
+ 1617 | await expect(
72
+ 1618 | adminPage.getByRole('group', { name: 'Parent category' }).getByRole('textbox'),
73
+ 1619 | ).toHaveValue(parentName);
74
+ 1620 | await expect(
75
+ 1621 | adminPage.getByRole('group', { name: 'Published' }).getByRole('textbox'),
76
+ 1622 | ).toHaveValue('Published');
77
+ 1623 | await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
78
+ 1624 | expect(adminPageErrors).toEqual([]);
79
+ 1625 | } finally {
80
+ 1626 | if (childId && headers) {
81
+ 1627 | const response = await adminPage.request.delete(`${categoryActionPath}/${childId}`, {
82
+ 1628 | headers,
83
+ 1629 | });
84
+ 1630 | expect(response.ok()).toBeTruthy();
85
+ 1631 | }
86
+ 1632 | if (parentId && headers) {
87
+ 1633 | const response = await adminPage.request.delete(`${categoryActionPath}/${parentId}`, {
88
+ 1634 | headers,
89
+ 1635 | });
90
+ 1636 | expect(response.ok()).toBeTruthy();
91
+ 1637 | }
92
+ 1638 | await adminContext.close().catch(() => {});
93
+ 1639 | }
94
+ 1640 | },
95
+ 1641 | );
96
+ 1642 |
97
+ 1643 | test(
98
+ 1644 | 'ATP-SPC-02: Product renders semantic Admin relation and publication controls',
99
+ 1645 | { tag: ['@admin', '@flow', '@product'] },
100
+ 1646 | async ({ browser }, testInfo) => {
101
+ 1647 | test.setTimeout(60_000);
102
+ 1648 | const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
103
+ 1649 | const categoryName = `E2E Product Category ${suffix}`;
104
+ 1650 | const productTitle = `E2E Product ${suffix}`;
105
+ 1651 | const adminContext = await browser.newContext();
106
+ 1652 | const adminPage = await adminContext.newPage();
107
+ 1653 | const adminPageErrors = collectPageErrors(adminPage);
108
+ 1654 | const productCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Aproduct/create';
109
+ 1655 | const productListPath = '/commerce-admin/rest/resource/commerce-catalog%3Aproduct';
110
+ 1656 | const categoryActionPath = '/api/commerce/catalog/category';
111
+ 1657 | const productActionPath = '/api/commerce/catalog/product';
112
+ 1658 | let categoryId: number | string | undefined;
113
+ 1659 | let productId: number | string | undefined;
114
+ 1660 | let headers: { Authorization: string } | undefined;
115
+ 1661 | try {
116
+ 1662 | await adminPage.setViewportSize({ width: 1440, height: 900 });
117
+ 1663 | await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
118
+ 1664 | const accessToken = (await adminContext.cookies()).find(
119
+ 1665 | cookie => cookie.name === 'token',
120
+ 1666 | )?.value;
121
+ 1667 | expect(accessToken).toBeTruthy();
122
+ 1668 | headers = { Authorization: `Bearer ${accessToken}` };
123
+ 1669 |
124
+ 1670 | const categoryResponse = await adminPage.request.post(categoryActionPath, {
125
+ 1671 | data: { name: categoryName, published: true },
126
+ 1672 | headers,
127
+ 1673 | });
128
+ 1674 | expect(categoryResponse.ok()).toBeTruthy();
129
+ 1675 | categoryId = (await categoryResponse.json()).data;
130
+ 1676 | expectTableIdentity(categoryId);
131
+ 1677 |
132
+ 1678 | await adminPage.goto(productCreatePath, { waitUntil: 'load' });
133
+ 1679 | const categoryPicker = adminPage
134
+ 1680 | .getByRole('group', { name: 'Category' })
135
+ 1681 | .getByRole('combobox');
136
+ > 1682 | await expect(categoryPicker).toBeVisible();
137
+ | ^ Error: expect(locator).toBeVisible() failed
138
+ 1683 | await categoryPicker.selectOption({ label: categoryName });
139
+ 1684 | await expect(categoryPicker).toHaveValue(String(categoryId));
140
+ 1685 | const publication = adminPage.getByRole('group', { name: 'Published' }).getByRole('combobox');
141
+ 1686 | await expect(publication).toBeVisible();
142
+ 1687 | await publication.selectOption({ label: 'Unpublished' });
143
+ 1688 | await expect(publication).toHaveValue('false');
144
+ 1689 |
145
+ 1690 | const productResponse = await adminPage.request.post(productActionPath, {
146
+ 1691 | data: { categoryId, title: productTitle, published: false },
147
+ 1692 | headers,
148
+ 1693 | });
149
+ 1694 | expect(productResponse.ok()).toBeTruthy();
150
+ 1695 | productId = (await productResponse.json()).data;
151
+ 1696 | expectTableIdentity(productId);
152
+ 1697 |
153
+ 1698 | await adminPage.goto(productListPath, { waitUntil: 'load' });
154
+ 1699 | const productRow = adminPage.getByRole('row', { name: new RegExp(productTitle) });
155
+ 1700 | await expect(productRow).toBeVisible();
156
+ 1701 | await expect(productRow.getByText(categoryName, { exact: true })).toBeVisible();
157
+ 1702 | await expect(productRow.getByText('Unpublished', { exact: true })).toBeVisible();
158
+ 1703 |
159
+ 1704 | await adminPage.goto(`${productListPath}/${productId}`, { waitUntil: 'load' });
160
+ 1705 | await expect(
161
+ 1706 | adminPage.getByRole('group', { name: 'Category' }).getByRole('textbox'),
162
+ 1707 | ).toHaveValue(categoryName);
163
+ 1708 | await expect(
164
+ 1709 | adminPage.getByRole('group', { name: 'Published' }).getByRole('textbox'),
165
+ 1710 | ).toHaveValue('Unpublished');
166
+ 1711 | await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
167
+ 1712 | expect(adminPageErrors).toEqual([]);
168
+ 1713 | } finally {
169
+ 1714 | if (productId && headers) {
170
+ 1715 | const response = await adminPage.request.delete(`${productActionPath}/${productId}`, {
171
+ 1716 | headers,
172
+ 1717 | });
173
+ 1718 | expect(response.ok()).toBeTruthy();
174
+ 1719 | }
175
+ 1720 | if (categoryId && headers) {
176
+ 1721 | const response = await adminPage.request.delete(`${categoryActionPath}/${categoryId}`, {
177
+ 1722 | headers,
178
+ 1723 | });
179
+ 1724 | expect(response.ok()).toBeTruthy();
180
+ 1725 | }
181
+ 1726 | await adminContext.close().catch(() => {});
182
+ 1727 | }
183
+ 1728 | },
184
+ 1729 | );
185
+ 1730 |
186
+ 1731 | test(
187
+ 1732 | 'ATP-SPC-02: SKU renders semantic Admin currency and lifecycle controls',
188
+ 1733 | { tag: ['@admin', '@flow', '@sku'] },
189
+ 1734 | async ({ browser }, testInfo) => {
190
+ 1735 | test.setTimeout(60_000);
191
+ 1736 | const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
192
+ 1737 | const categoryName = `E2E SKU Category ${suffix}`;
193
+ 1738 | const productTitle = `E2E SKU Product ${suffix}`;
194
+ 1739 | const skuCode = `E2E-SKU-${suffix}`;
195
+ 1740 | const stockActionPath = '/api/commerce/trade/stockBalance/adjustStock';
196
+ 1741 | const adminContext = await browser.newContext();
197
+ 1742 | const adminPage = await adminContext.newPage();
198
+ 1743 | const adminPageErrors = collectPageErrors(adminPage);
199
+ 1744 | const skuCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Asku/create';
200
+ 1745 | const skuListPath = '/commerce-admin/rest/resource/commerce-catalog%3Asku';
201
+ 1746 | const categoryActionPath = '/api/commerce/catalog/category';
202
+ 1747 | const productActionPath = '/api/commerce/catalog/product';
203
+ 1748 | const skuActionPath = '/api/commerce/catalog/sku';
204
+ 1749 | let categoryId: number | string | undefined;
205
+ 1750 | let productId: number | string | undefined;
206
+ 1751 | let skuId: number | string | undefined;
207
+ 1752 | let headers: { Authorization: string } | undefined;
208
+ 1753 | try {
209
+ 1754 | await adminPage.setViewportSize({ width: 1440, height: 900 });
210
+ 1755 | await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
211
+ 1756 | const accessToken = (await adminContext.cookies()).find(
212
+ 1757 | cookie => cookie.name === 'token',
213
+ 1758 | )?.value;
214
+ 1759 | expect(accessToken).toBeTruthy();
215
+ 1760 | headers = { Authorization: `Bearer ${accessToken}` };
216
+ 1761 |
217
+ 1762 | const categoryResponse = await adminPage.request.post(categoryActionPath, {
218
+ 1763 | data: { name: categoryName, published: true },
219
+ 1764 | headers,
220
+ 1765 | });
221
+ 1766 | expect(categoryResponse.ok()).toBeTruthy();
222
+ 1767 | categoryId = (await categoryResponse.json()).data;
223
+ 1768 | expectTableIdentity(categoryId);
224
+ 1769 |
225
+ 1770 | const productResponse = await adminPage.request.post(productActionPath, {
226
+ 1771 | data: { categoryId, title: productTitle, published: true },
227
+ 1772 | headers,
228
+ 1773 | });
229
+ 1774 | expect(productResponse.ok()).toBeTruthy();
230
+ 1775 | productId = (await productResponse.json()).data;
231
+ 1776 | expectTableIdentity(productId);
232
+ 1777 |
233
+ 1778 | await adminPage.goto(skuCreatePath, { waitUntil: 'load' });
234
+ 1779 | const productField = adminPage.getByRole('group', { name: 'Product' });
235
+ 1780 | await expect(productField).toBeVisible();
236
+ 1781 | const productPicker = productField.getByRole('combobox');
237
+ 1782 | await expect(productPicker).toBeVisible();
238
+ ```