@nextsparkjs/theme-default 0.1.0-beta.20 → 0.1.0-beta.21

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 (220) hide show
  1. package/package.json +1 -1
  2. package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
  3. package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
  4. package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
  5. package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
  6. package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
  7. package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
  8. package/tests/cypress/e2e/_docs/README.md +215 -0
  9. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
  10. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
  11. package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
  12. package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
  13. package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
  14. package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
  15. package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
  16. package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
  17. package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
  18. package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
  19. package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
  20. package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
  21. package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
  22. package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
  23. package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
  24. package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
  25. package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
  26. package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
  27. package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
  28. package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
  29. package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
  30. package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
  31. package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
  32. package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
  33. package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
  34. package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
  35. package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
  36. package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
  37. package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
  38. package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
  39. package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
  40. package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
  41. package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
  42. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  43. package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
  44. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  45. package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
  46. package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
  47. package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
  48. package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
  49. package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
  50. package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
  51. package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
  52. package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
  53. package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
  54. package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
  55. package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
  56. package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
  57. package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
  58. package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
  59. package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
  60. package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
  61. package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
  62. package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
  63. package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
  64. package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
  65. package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
  66. package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
  67. package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
  68. package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
  69. package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
  70. package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
  71. package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
  72. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
  73. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
  74. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  75. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  76. package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
  77. package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
  78. package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
  79. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
  80. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
  81. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
  82. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
  83. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
  84. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
  85. package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
  86. package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
  87. package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
  88. package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
  89. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
  90. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
  91. package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
  92. package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
  93. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
  94. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
  95. package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
  96. package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
  97. package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
  98. package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
  99. package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
  100. package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
  101. package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
  102. package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
  103. package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
  104. package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
  105. package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
  106. package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
  107. package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
  108. package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
  109. package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
  110. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  111. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  112. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  113. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  114. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  115. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  116. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  117. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  118. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  119. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  120. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  121. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  122. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  123. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  124. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  125. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  126. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  127. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  128. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  129. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  130. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  131. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  132. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  133. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  134. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  135. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  136. package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
  137. package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
  138. package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
  139. package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
  140. package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
  141. package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
  142. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
  143. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
  144. package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
  145. package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
  146. package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
  147. package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
  148. package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
  149. package/tests/cypress/fixtures/blocks.json +218 -0
  150. package/tests/cypress/fixtures/entities.json +78 -0
  151. package/tests/cypress/fixtures/page-builder.json +21 -0
  152. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  153. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  154. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  155. package/tests/cypress/src/components/EntityForm.ts +375 -0
  156. package/tests/cypress/src/components/EntityList.ts +389 -0
  157. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  158. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  159. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  160. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  161. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  162. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  163. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  164. package/tests/cypress/src/components/index.ts +21 -0
  165. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  166. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  167. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  168. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  169. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  170. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  171. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  172. package/tests/cypress/src/controllers/index.js +25 -0
  173. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  174. package/tests/cypress/src/core/BasePOM.ts +86 -0
  175. package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  176. package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
  177. package/tests/cypress/src/core/index.ts +14 -0
  178. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  179. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  180. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  181. package/tests/cypress/src/entities/TasksPOM.ts +176 -0
  182. package/tests/cypress/src/entities/index.ts +14 -0
  183. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  184. package/tests/cypress/src/features/DashboardPOM.ts +245 -0
  185. package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
  186. package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
  187. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  188. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  189. package/tests/cypress/src/features/SettingsPOM.ts +362 -0
  190. package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  191. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  192. package/tests/cypress/src/features/index.ts +28 -0
  193. package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
  194. package/tests/cypress/src/index.ts +101 -0
  195. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  196. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  197. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  198. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  199. package/tests/cypress/src/pages/index.js +5 -0
  200. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  201. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  202. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  203. package/tests/cypress/src/pages/public/index.js +6 -0
  204. package/tests/cypress/src/selectors.ts +46 -0
  205. package/tests/cypress/src/session-helpers.ts +500 -0
  206. package/tests/cypress/support/doc-commands.ts +260 -0
  207. package/tests/cypress.config.ts +150 -0
  208. package/tests/jest/components/post-header.test.tsx +377 -0
  209. package/tests/jest/config/role-config.test.ts +529 -0
  210. package/tests/jest/jest.config.ts +81 -0
  211. package/tests/jest/langchain/COVERAGE.md +372 -0
  212. package/tests/jest/langchain/guardrails.test.ts +465 -0
  213. package/tests/jest/langchain/streaming.test.ts +367 -0
  214. package/tests/jest/langchain/token-tracker.test.ts +455 -0
  215. package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
  216. package/tests/jest/langchain/tracer.test.ts +823 -0
  217. package/tests/jest/user-roles/role-helpers.test.ts +432 -0
  218. package/tests/jest/validation/categories.test.ts +429 -0
  219. package/tests/jest/validation/posts.test.ts +546 -0
  220. package/tests/tsconfig.json +15 -0
@@ -0,0 +1,710 @@
1
+ /**
2
+ * Page Builder POM
3
+ *
4
+ * Page Object Model for the Block Editor / Page Builder system.
5
+ * Covers admin UI workflows for creating and editing pages with blocks.
6
+ *
7
+ * Convention: block-editor-{component}-{detail}
8
+ * Based on 67+ existing data-cy attributes in dashboard components.
9
+ */
10
+
11
+ import { ApiInterceptor } from '../helpers/ApiInterceptor'
12
+
13
+ export interface PageFormData {
14
+ title: string
15
+ slug: string
16
+ locale?: 'en' | 'es'
17
+ status?: 'draft' | 'published' | 'scheduled' | 'archived'
18
+ metaTitle?: string
19
+ metaDescription?: string
20
+ }
21
+
22
+ export interface BlockData {
23
+ slug: string
24
+ props?: Record<string, unknown>
25
+ }
26
+
27
+ export class PageBuilderPOM {
28
+ // ============================================
29
+ // STATIC CONFIG
30
+ // ============================================
31
+
32
+ static get slug() {
33
+ return 'pages'
34
+ }
35
+
36
+ // ============================================
37
+ // API INTERCEPTOR (for deterministic waits)
38
+ // ============================================
39
+
40
+ private static _api: ApiInterceptor | null = null
41
+
42
+ /**
43
+ * Get the API interceptor instance for pages
44
+ * Lazy-initialized on first access
45
+ */
46
+ static get api(): ApiInterceptor {
47
+ if (!this._api) {
48
+ this._api = new ApiInterceptor(this.slug)
49
+ }
50
+ return this._api
51
+ }
52
+
53
+ /**
54
+ * Setup API intercepts for CRUD operations
55
+ * Call this in beforeEach BEFORE navigation
56
+ */
57
+ static setupApiIntercepts(): typeof PageBuilderPOM {
58
+ this.api.setupCrudIntercepts()
59
+ return this
60
+ }
61
+
62
+ // ============================================
63
+ // SELECTORS - Pages List
64
+ // ============================================
65
+
66
+ static get listSelectors() {
67
+ // Using entity testing convention: {slug}-{component}
68
+ // Based on createCyId(entityConfig.slug, component) from testing-utils.ts
69
+ return {
70
+ page: '[data-cy="pages-page"]',
71
+ title: '[data-cy="pages-title"]',
72
+ table: '[data-cy="pages-table"], table',
73
+ createBtn: '[data-cy="pages-add"]',
74
+ searchContainer: '[data-cy="pages-search"]',
75
+ searchInput: '[data-cy="pages-search-input"]',
76
+ filterStatus: '[data-cy="pages-filter-status"]',
77
+ filterLocale: '[data-cy="pages-filter-locale"]',
78
+ row: (id: string) => `[data-cy="pages-row-${id}"]`,
79
+ rowGeneric: 'table tbody tr',
80
+ // Row menu actions (EntityTable patterns)
81
+ menuTrigger: (id: string) => `[data-cy="pages-menu-${id}"]`,
82
+ menuEdit: (id: string) => `[data-cy="pages-menu-edit-${id}"]`,
83
+ menuDelete: (id: string) => `[data-cy="pages-menu-delete-${id}"]`,
84
+ menuView: (id: string) => `[data-cy="pages-menu-view-${id}"]`,
85
+ confirmDelete: '[data-cy="pages-confirm-delete"], [role="dialog"]',
86
+ confirmDeleteBtn: '[data-cy="pages-confirm-delete-btn"], [role="dialog"] button:contains("Delete"), button.bg-destructive',
87
+ cancelDeleteBtn: '[data-cy="pages-cancel-delete-btn"], [role="dialog"] button:contains("Cancel"),[role="dialog"] button[type="button"]:not(.bg-destructive)',
88
+ emptyState: 'td:contains("No pages")',
89
+ }
90
+ }
91
+
92
+ // ============================================
93
+ // SELECTORS - Block Editor
94
+ // ============================================
95
+
96
+ static get editorSelectors() {
97
+ return {
98
+ // Page container (builder-editor is the main editor wrapper)
99
+ editorPage: '[data-cy="builder-editor"]',
100
+
101
+ // Left sidebar toggle (expands/collapses block picker)
102
+ leftSidebarToggle: '[data-cy="left-sidebar-toggle"]',
103
+
104
+ // Mode toggles (wrapper and individual buttons)
105
+ viewModeToggle: '[data-cy="view-mode-toggle"]',
106
+ modeLayout: '[data-cy="mode-layout"]',
107
+ modePreview: '[data-cy="mode-preview"]',
108
+
109
+ // Block Picker (Left Panel)
110
+ blockPicker: '[data-cy="block-picker"]',
111
+ blockSearchInput: '[data-cy="block-search-input"]',
112
+ categoryTab: (cat: string) => `[data-cy="category-${cat}"]`,
113
+ blockItem: (slug: string) => `[data-cy="block-item-${slug}"]`,
114
+ addBlockBtn: (slug: string) => `[data-cy="add-block-${slug}"]`,
115
+
116
+ // Block Canvas (Center Panel - Edit Mode)
117
+ blockCanvas: '[data-cy="block-canvas"]',
118
+ blockCanvasEmpty: '[data-cy="block-canvas-empty"]',
119
+ sortableBlock: (id: string) => `[data-cy="sortable-block-${id}"]`,
120
+ dragHandle: (id: string) => `[data-cy="drag-handle-${id}"]`,
121
+ duplicateBlock: (id: string) => `[data-cy="duplicate-block-${id}"]`,
122
+ removeBlock: (id: string) => `[data-cy="remove-block-${id}"]`,
123
+ sortableBlockGeneric: '[data-cy^="sortable-block-"]',
124
+
125
+ // Preview Canvas (Center Panel - Preview Mode)
126
+ previewCanvas: '[data-cy="block-preview-canvas"]',
127
+ previewCanvasEmpty: '[data-cy="block-preview-canvas-empty"]',
128
+ previewBlock: (id: string) => `[data-cy="preview-block-${id}"]`,
129
+ moveUpBtn: (id: string) => `[data-cy="preview-block-${id}-move-up"]`,
130
+ moveDownBtn: (id: string) => `[data-cy="preview-block-${id}-move-down"]`,
131
+ previewBlockGeneric: '[data-cy^="preview-block-"]',
132
+
133
+ // Settings Panel (Right Panel)
134
+ settingsPanel: '[data-cy="block-settings-panel"]',
135
+ settingsEmpty: '[data-cy="settings-panel-empty"]',
136
+ resetPropsBtn: '[data-cy="reset-block-props"]',
137
+ removeBlockSettings: '[data-cy="remove-block-settings"]',
138
+
139
+ // Settings Tabs
140
+ tabContent: '[data-cy="tab-content"]',
141
+ tabDesign: '[data-cy="tab-design"]',
142
+ tabAdvanced: '[data-cy="tab-advanced"]',
143
+
144
+ // Field inputs in settings
145
+ fieldInput: (name: string) => `[data-cy="field-${name}"]`,
146
+ fieldTextarea: (name: string) => `[data-cy="field-${name}"] textarea`,
147
+ fieldSelect: (name: string) => `[data-cy="field-${name}"] [role="combobox"]`,
148
+ fieldCheckbox: (name: string) => `[data-cy="field-${name}"] input[type="checkbox"]`,
149
+ fieldArray: (name: string) => `[data-cy="field-${name}-array"]`,
150
+
151
+ // Array field controls
152
+ arrayAddItem: (name: string) => `[data-cy="field-${name}-add-item"]`,
153
+ arrayItem: (name: string, index: number) => `[data-cy="field-${name}-item-${index}"]`,
154
+ arrayItemRemove: (name: string, index: number) => `[data-cy="field-${name}-item-${index}-remove"]`,
155
+ arrayItemMoveUp: (name: string, index: number) => `[data-cy="field-${name}-item-${index}-move-up"]`,
156
+ arrayItemMoveDown: (name: string, index: number) => `[data-cy="field-${name}-item-${index}-move-down"]`,
157
+
158
+ // CTA group fields (collapsible)
159
+ ctaGroupTrigger: (name: string) => `[data-cy="cta-group-${name}"]`,
160
+ ctaGroupContent: (name: string) => `[data-cy="cta-group-${name}-content"]`,
161
+ }
162
+ }
163
+
164
+ // ============================================
165
+ // SELECTORS - Page Settings
166
+ // ============================================
167
+
168
+ static get pageSettingsSelectors() {
169
+ return {
170
+ panel: '[data-cy="page-settings-panel"]',
171
+ // Note: In editor, these are in the top bar
172
+ titleInput: '[data-cy="editor-title-input"]',
173
+ slugInput: '[data-cy="editor-slug-input"]',
174
+ localeSelect: '[data-cy="locale-select"]',
175
+
176
+ // Status selector (dropdown)
177
+ statusSelector: '[data-cy="status-selector"]',
178
+ statusOption: (status: string) => `[data-cy="status-option-${status}"]`,
179
+ statusBadge: '[data-cy="status-badge"]',
180
+
181
+ // SEO Settings
182
+ seoTrigger: '[data-cy="seo-settings-trigger"]',
183
+ seoContent: '[data-cy="seo-settings-content"]',
184
+ metaTitle: '[data-cy="seo-meta-title"]',
185
+ metaDescription: '[data-cy="seo-meta-description"]',
186
+
187
+ // Action buttons
188
+ saveBtn: '[data-cy="save-btn"]',
189
+ previewBtn: '[data-cy="preview-page-btn"]',
190
+ deleteBtn: '[data-cy="delete-page-btn"]',
191
+ }
192
+ }
193
+
194
+ // ============================================
195
+ // NAVIGATION
196
+ // ============================================
197
+
198
+ static visitList() {
199
+ cy.visit('/dashboard/pages')
200
+ return this
201
+ }
202
+
203
+ static visitCreate() {
204
+ cy.visit('/dashboard/pages/create')
205
+ return this
206
+ }
207
+
208
+ static visitEdit(id: string) {
209
+ cy.visit(`/dashboard/pages/${id}/edit`)
210
+ return this
211
+ }
212
+
213
+ // ============================================
214
+ // API-AWARE NAVIGATION
215
+ // ============================================
216
+
217
+ /**
218
+ * Visit list page with API intercepts and wait for data load
219
+ */
220
+ static visitListWithApiWait(): typeof PageBuilderPOM {
221
+ this.setupApiIntercepts()
222
+ this.visitList()
223
+ this.api.waitForList()
224
+ return this
225
+ }
226
+
227
+ /**
228
+ * Visit edit page with API intercepts
229
+ */
230
+ static visitEditWithApiWait(id: string): typeof PageBuilderPOM {
231
+ this.setupApiIntercepts()
232
+ this.visitEdit(id)
233
+ return this
234
+ }
235
+
236
+ // ============================================
237
+ // WAIT METHODS
238
+ // ============================================
239
+
240
+ static waitForListLoad() {
241
+ cy.url().should('include', '/dashboard/pages')
242
+ cy.get(this.listSelectors.page, { timeout: 15000 }).should('exist')
243
+ return this
244
+ }
245
+
246
+ static waitForEditorLoad() {
247
+ cy.get(this.editorSelectors.editorPage, { timeout: 15000 }).should('be.visible')
248
+ return this
249
+ }
250
+
251
+ static waitForBlockPickerLoad() {
252
+ cy.get(this.editorSelectors.blockPicker, { timeout: 10000 }).should('be.visible')
253
+ return this
254
+ }
255
+
256
+ static waitForSettingsPanelLoad() {
257
+ cy.get(this.editorSelectors.settingsPanel, { timeout: 10000 }).should('be.visible')
258
+ return this
259
+ }
260
+
261
+ // ============================================
262
+ // LIST PAGE INTERACTIONS
263
+ // ============================================
264
+
265
+ static clickCreatePage() {
266
+ cy.get(this.listSelectors.createBtn).click()
267
+ return this
268
+ }
269
+
270
+ static searchPages(term: string) {
271
+ cy.get(this.listSelectors.searchInput).clear().type(term)
272
+ return this
273
+ }
274
+
275
+ static clearSearch() {
276
+ cy.get(this.listSelectors.searchInput).clear()
277
+ return this
278
+ }
279
+
280
+ static filterByStatus(status: 'all' | 'published' | 'draft') {
281
+ cy.get(this.listSelectors.filterStatus).click()
282
+ cy.contains('[role="option"]', status).click()
283
+ return this
284
+ }
285
+
286
+ static openRowMenu(id: string) {
287
+ cy.get(this.listSelectors.menuTrigger(id)).click()
288
+ return this
289
+ }
290
+
291
+ static clickMenuEdit(id: string) {
292
+ cy.get(this.listSelectors.menuEdit(id)).click()
293
+ return this
294
+ }
295
+
296
+ static clickMenuDelete(id: string) {
297
+ cy.get(this.listSelectors.menuDelete(id)).click()
298
+ return this
299
+ }
300
+
301
+ static clickMenuView(id: string) {
302
+ cy.get(this.listSelectors.menuView(id)).click()
303
+ return this
304
+ }
305
+
306
+ static confirmDelete() {
307
+ cy.get(this.listSelectors.confirmDeleteBtn).click()
308
+ return this
309
+ }
310
+
311
+ static cancelDelete() {
312
+ cy.get(this.listSelectors.cancelDeleteBtn).click()
313
+ return this
314
+ }
315
+
316
+ // ============================================
317
+ // BLOCK EDITOR INTERACTIONS
318
+ // ============================================
319
+
320
+ /**
321
+ * Ensures the block picker sidebar is visible (expanded)
322
+ * If not visible, clicks the toggle to expand it
323
+ */
324
+ static ensureBlockPickerVisible() {
325
+ // Use invoke to get a primitive boolean, avoiding allure-cypress serialization issues
326
+ cy.get(this.editorSelectors.blockPicker, { timeout: 1000 })
327
+ .should('exist')
328
+ .invoke('is', ':visible')
329
+ .then((isVisible: boolean) => {
330
+ if (!isVisible) {
331
+ cy.get(this.editorSelectors.leftSidebarToggle).click()
332
+ cy.get(this.editorSelectors.blockPicker, { timeout: 5000 }).should('be.visible')
333
+ }
334
+ })
335
+ return this
336
+ }
337
+
338
+ static switchToLayoutMode() {
339
+ cy.get(this.editorSelectors.modeLayout).click()
340
+ // Ensure block picker is visible after switching to layout mode
341
+ this.ensureBlockPickerVisible()
342
+ return this
343
+ }
344
+
345
+ static switchToPreviewMode() {
346
+ cy.get(this.editorSelectors.modePreview).click()
347
+ return this
348
+ }
349
+
350
+ static searchBlocks(term: string) {
351
+ this.ensureBlockPickerVisible()
352
+ cy.get(this.editorSelectors.blockSearchInput).clear().type(term)
353
+ return this
354
+ }
355
+
356
+ static selectBlockCategory(category: string) {
357
+ this.ensureBlockPickerVisible()
358
+ cy.get(this.editorSelectors.categoryTab(category)).click()
359
+ return this
360
+ }
361
+
362
+ static addBlock(slug: string) {
363
+ this.ensureBlockPickerVisible()
364
+ // Click on block item (not the hidden add button) to add block
365
+ // The block item div is always visible and has onClick handler
366
+ cy.get(this.editorSelectors.blockItem(slug))
367
+ .scrollIntoView()
368
+ .should('be.visible')
369
+ .click()
370
+ return this
371
+ }
372
+
373
+ static selectBlock(blockId: string) {
374
+ cy.get(this.editorSelectors.sortableBlock(blockId)).click()
375
+ return this
376
+ }
377
+
378
+ static selectPreviewBlock(blockId: string) {
379
+ cy.get(this.editorSelectors.previewBlock(blockId)).click()
380
+ return this
381
+ }
382
+
383
+ static duplicateBlock(blockId: string) {
384
+ cy.get(this.editorSelectors.duplicateBlock(blockId)).click()
385
+ return this
386
+ }
387
+
388
+ static removeBlock(blockId: string) {
389
+ cy.get(this.editorSelectors.removeBlock(blockId)).click()
390
+ return this
391
+ }
392
+
393
+ static moveBlockUp(blockId: string) {
394
+ cy.get(this.editorSelectors.moveUpBtn(blockId)).click()
395
+ return this
396
+ }
397
+
398
+ static moveBlockDown(blockId: string) {
399
+ cy.get(this.editorSelectors.moveDownBtn(blockId)).click()
400
+ return this
401
+ }
402
+
403
+ // ============================================
404
+ // SETTINGS PANEL INTERACTIONS
405
+ // ============================================
406
+
407
+ static selectSettingsTab(tab: 'content' | 'design' | 'advanced') {
408
+ const selector =
409
+ tab === 'content'
410
+ ? this.editorSelectors.tabContent
411
+ : tab === 'design'
412
+ ? this.editorSelectors.tabDesign
413
+ : this.editorSelectors.tabAdvanced
414
+ cy.get(selector).click()
415
+ return this
416
+ }
417
+
418
+ static fillField(name: string, value: string) {
419
+ cy.get(this.editorSelectors.fieldInput(name)).find('input, textarea').first().clear().type(value)
420
+ return this
421
+ }
422
+
423
+ static fillTextarea(name: string, value: string) {
424
+ cy.get(this.editorSelectors.fieldTextarea(name)).clear().type(value)
425
+ return this
426
+ }
427
+
428
+ static selectOption(name: string, value: string) {
429
+ cy.get(this.editorSelectors.fieldSelect(name)).click()
430
+ cy.contains('[role="option"]', value).click()
431
+ return this
432
+ }
433
+
434
+ static toggleCheckbox(name: string) {
435
+ cy.get(this.editorSelectors.fieldCheckbox(name)).click()
436
+ return this
437
+ }
438
+
439
+ static resetBlockProps() {
440
+ cy.get(this.editorSelectors.resetPropsBtn).click()
441
+ return this
442
+ }
443
+
444
+ static removeBlockFromSettings() {
445
+ cy.get(this.editorSelectors.removeBlockSettings).click()
446
+ return this
447
+ }
448
+
449
+ // ============================================
450
+ // ARRAY FIELD INTERACTIONS
451
+ // ============================================
452
+
453
+ static addArrayItem(fieldName: string) {
454
+ cy.get(this.editorSelectors.arrayAddItem(fieldName)).click()
455
+ return this
456
+ }
457
+
458
+ static removeArrayItem(fieldName: string, index: number) {
459
+ cy.get(this.editorSelectors.arrayItemRemove(fieldName, index)).click()
460
+ return this
461
+ }
462
+
463
+ static moveArrayItemUp(fieldName: string, index: number) {
464
+ cy.get(this.editorSelectors.arrayItemMoveUp(fieldName, index)).click()
465
+ return this
466
+ }
467
+
468
+ static moveArrayItemDown(fieldName: string, index: number) {
469
+ cy.get(this.editorSelectors.arrayItemMoveDown(fieldName, index)).click()
470
+ return this
471
+ }
472
+
473
+ // ============================================
474
+ // CTA GROUP INTERACTIONS
475
+ // ============================================
476
+
477
+ static toggleCtaGroup(name: string) {
478
+ cy.get(this.editorSelectors.ctaGroupTrigger(name)).click()
479
+ return this
480
+ }
481
+
482
+ // ============================================
483
+ // PAGE SETTINGS INTERACTIONS
484
+ // ============================================
485
+
486
+ static setPageTitle(title: string) {
487
+ cy.get(this.pageSettingsSelectors.titleInput).clear().type(title)
488
+ return this
489
+ }
490
+
491
+ static setPageSlug(slug: string) {
492
+ cy.get(this.pageSettingsSelectors.slugInput).clear().type(slug)
493
+ return this
494
+ }
495
+
496
+ static setPageLocale(locale: 'en' | 'es') {
497
+ cy.get(this.pageSettingsSelectors.localeSelect).click()
498
+ cy.contains('[role="option"]', locale).click()
499
+ return this
500
+ }
501
+
502
+ /**
503
+ * Select a status from the dropdown
504
+ */
505
+ static selectStatus(status: 'draft' | 'published' | 'scheduled' | 'archived') {
506
+ // Status selector may be partially outside viewport, use force click
507
+ cy.get(this.pageSettingsSelectors.statusSelector)
508
+ .should('exist')
509
+ .click({ force: true })
510
+ cy.get(this.pageSettingsSelectors.statusOption(status)).click()
511
+ return this
512
+ }
513
+
514
+ static openSeoSettings() {
515
+ cy.get(this.pageSettingsSelectors.seoTrigger).click()
516
+ return this
517
+ }
518
+
519
+ static setMetaTitle(title: string) {
520
+ cy.get(this.pageSettingsSelectors.metaTitle).clear().type(title)
521
+ return this
522
+ }
523
+
524
+ static setMetaDescription(description: string) {
525
+ cy.get(this.pageSettingsSelectors.metaDescription).clear().type(description)
526
+ return this
527
+ }
528
+
529
+ static savePage() {
530
+ // Wait for any pending state updates after adding blocks
531
+ cy.wait(500)
532
+ // The save button might be partially outside viewport, use force click
533
+ cy.get(this.pageSettingsSelectors.saveBtn)
534
+ .should('exist')
535
+ .click({ force: true })
536
+ return this
537
+ }
538
+
539
+ /**
540
+ * Save page and wait for API response (deterministic)
541
+ */
542
+ static savePageWithApiWait(): typeof PageBuilderPOM {
543
+ this.savePage()
544
+ this.api.waitForUpdate()
545
+ return this
546
+ }
547
+
548
+ /**
549
+ * Change status to published and save
550
+ */
551
+ static publishPage() {
552
+ this.selectStatus('published')
553
+ this.savePage()
554
+ return this
555
+ }
556
+
557
+ /**
558
+ * Change status to draft and save (unpublish)
559
+ */
560
+ static unpublishPage() {
561
+ this.selectStatus('draft')
562
+ this.savePage()
563
+ return this
564
+ }
565
+
566
+ static previewPage() {
567
+ cy.get(this.pageSettingsSelectors.previewBtn).click()
568
+ return this
569
+ }
570
+
571
+ static deletePage() {
572
+ cy.get(this.pageSettingsSelectors.deleteBtn).click()
573
+ return this
574
+ }
575
+
576
+ // ============================================
577
+ // COMPLETE WORKFLOWS
578
+ // ============================================
579
+
580
+ /**
581
+ * Create a new page with specified data and blocks
582
+ */
583
+ static createPage(data: PageFormData, blocks: BlockData[] = []) {
584
+ this.visitCreate()
585
+ this.waitForEditorLoad()
586
+
587
+ // Set page settings
588
+ this.setPageTitle(data.title)
589
+ this.setPageSlug(data.slug)
590
+
591
+ if (data.locale) {
592
+ this.setPageLocale(data.locale)
593
+ }
594
+
595
+ // Add blocks
596
+ blocks.forEach((block) => {
597
+ this.addBlock(block.slug)
598
+ })
599
+
600
+ // Set status if provided
601
+ if (data.status) {
602
+ this.selectStatus(data.status)
603
+ }
604
+
605
+ // Set SEO if provided
606
+ if (data.metaTitle || data.metaDescription) {
607
+ this.openSeoSettings()
608
+ if (data.metaTitle) this.setMetaTitle(data.metaTitle)
609
+ if (data.metaDescription) this.setMetaDescription(data.metaDescription)
610
+ }
611
+
612
+ this.savePage()
613
+ return this
614
+ }
615
+
616
+ // ============================================
617
+ // ASSERTIONS
618
+ // ============================================
619
+
620
+ static assertListPageVisible() {
621
+ cy.get(this.listSelectors.page).should('exist')
622
+ return this
623
+ }
624
+
625
+ static assertEditorVisible() {
626
+ cy.get(this.editorSelectors.editorPage).should('be.visible')
627
+ return this
628
+ }
629
+
630
+ static assertBlockPickerVisible() {
631
+ cy.get(this.editorSelectors.blockPicker).should('be.visible')
632
+ return this
633
+ }
634
+
635
+ static assertBlockInCanvas(blockSlug: string) {
636
+ cy.get(this.editorSelectors.blockCanvas).should('contain.text', blockSlug)
637
+ return this
638
+ }
639
+
640
+ static assertBlockCount(count: number) {
641
+ cy.get(this.editorSelectors.sortableBlockGeneric).should('have.length', count)
642
+ return this
643
+ }
644
+
645
+ static assertPreviewBlockCount(count: number) {
646
+ cy.get(this.editorSelectors.previewBlockGeneric).should('have.length', count)
647
+ return this
648
+ }
649
+
650
+ static assertCanvasEmpty() {
651
+ cy.get(this.editorSelectors.blockCanvasEmpty).should('be.visible')
652
+ return this
653
+ }
654
+
655
+ static assertSettingsPanelEmpty() {
656
+ cy.get(this.editorSelectors.settingsEmpty).should('be.visible')
657
+ return this
658
+ }
659
+
660
+ static assertSettingsPanelHasContent() {
661
+ cy.get(this.editorSelectors.settingsPanel).should('be.visible')
662
+ cy.get(this.editorSelectors.settingsEmpty).should('not.exist')
663
+ return this
664
+ }
665
+
666
+ static assertBlockInPicker(blockSlug: string) {
667
+ // Scroll into view within the block picker scroll area
668
+ cy.get(this.editorSelectors.blockItem(blockSlug))
669
+ .scrollIntoView()
670
+ .should('be.visible')
671
+ return this
672
+ }
673
+
674
+ static assertPageInList(title: string) {
675
+ cy.contains(this.listSelectors.rowGeneric, title).should('be.visible')
676
+ return this
677
+ }
678
+
679
+ static assertPageNotInList(title: string) {
680
+ cy.contains(this.listSelectors.rowGeneric, title).should('not.exist')
681
+ return this
682
+ }
683
+
684
+ static assertEmptyList() {
685
+ cy.get(this.listSelectors.emptyState).should('be.visible')
686
+ return this
687
+ }
688
+
689
+ static assertSaveSuccess() {
690
+ cy.contains('saved', { matchCase: false }).should('be.visible')
691
+ return this
692
+ }
693
+
694
+ static assertPublishSuccess() {
695
+ cy.contains('saved', { matchCase: false }).should('be.visible')
696
+ return this
697
+ }
698
+
699
+ static assertStatusBadge(status: 'draft' | 'published' | 'scheduled' | 'archived') {
700
+ cy.get(this.pageSettingsSelectors.statusBadge).should('contain.text', status)
701
+ return this
702
+ }
703
+
704
+ static assertStatusSelected(status: string) {
705
+ cy.get(this.pageSettingsSelectors.statusSelector).should('contain.text', status)
706
+ return this
707
+ }
708
+ }
709
+
710
+ export default PageBuilderPOM