@nextsparkjs/core 0.1.0-beta.39 → 0.1.0-beta.40

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 (57) hide show
  1. package/dist/styles/classes.json +1 -1
  2. package/dist/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.bdd.md +278 -0
  3. package/dist/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +22 -14
  4. package/dist/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  5. package/dist/templates/contents/themes/starter/tests/cypress/src/components/EntityForm.ts +375 -0
  6. package/dist/templates/contents/themes/starter/tests/cypress/src/components/EntityList.ts +389 -0
  7. package/dist/templates/contents/themes/starter/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  8. package/dist/templates/contents/themes/starter/tests/cypress/src/components/index.ts +13 -0
  9. package/dist/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  10. package/dist/templates/contents/themes/starter/tests/cypress/src/core/index.ts +2 -0
  11. package/dist/templates/contents/themes/starter/tests/cypress/{e2e/uat/entities/tasks → src/entities}/TasksPOM.ts +1 -1
  12. package/dist/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +10 -0
  13. package/dist/templates/contents/themes/starter/tests/cypress/src/features/BillingPOM.ts +385 -0
  14. package/dist/templates/contents/themes/starter/tests/cypress/src/features/DashboardPOM.ts +245 -0
  15. package/dist/templates/contents/themes/starter/tests/cypress/src/features/DevtoolsPOM.ts +750 -0
  16. package/dist/templates/contents/themes/starter/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  17. package/dist/templates/contents/themes/starter/tests/cypress/src/features/SettingsPOM.ts +362 -0
  18. package/dist/templates/contents/themes/starter/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  19. package/dist/templates/contents/themes/starter/tests/cypress/src/features/index.ts +18 -0
  20. package/dist/templates/contents/themes/starter/tests/cypress/src/index.ts +88 -0
  21. package/dist/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +332 -88
  22. package/dist/templates/contents/themes/starter/tests/cypress.config.ts +4 -1
  23. package/package.json +1 -1
  24. package/scripts/test/jest-theme.mjs +7 -3
  25. package/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.bdd.md +278 -0
  26. package/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +22 -14
  27. package/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  28. package/templates/contents/themes/starter/tests/cypress/src/components/EntityForm.ts +375 -0
  29. package/templates/contents/themes/starter/tests/cypress/src/components/EntityList.ts +389 -0
  30. package/templates/contents/themes/starter/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  31. package/templates/contents/themes/starter/tests/cypress/src/components/index.ts +13 -0
  32. package/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  33. package/templates/contents/themes/starter/tests/cypress/src/core/index.ts +2 -0
  34. package/templates/contents/themes/starter/tests/cypress/{e2e/uat/entities/tasks → src/entities}/TasksPOM.ts +1 -1
  35. package/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +10 -0
  36. package/templates/contents/themes/starter/tests/cypress/src/features/BillingPOM.ts +385 -0
  37. package/templates/contents/themes/starter/tests/cypress/src/features/DashboardPOM.ts +245 -0
  38. package/templates/contents/themes/starter/tests/cypress/src/features/DevtoolsPOM.ts +750 -0
  39. package/templates/contents/themes/starter/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  40. package/templates/contents/themes/starter/tests/cypress/src/features/SettingsPOM.ts +362 -0
  41. package/templates/contents/themes/starter/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  42. package/templates/contents/themes/starter/tests/cypress/src/features/index.ts +18 -0
  43. package/templates/contents/themes/starter/tests/cypress/src/index.ts +88 -0
  44. package/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +332 -88
  45. package/templates/contents/themes/starter/tests/cypress.config.ts +4 -1
  46. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/pages-editor.bdd.md +0 -207
  47. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/pages-editor.cy.ts +0 -211
  48. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/posts-editor.bdd.md +0 -184
  49. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/posts-editor.cy.ts +0 -350
  50. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/public.cy.ts +0 -112
  51. package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/taxonomies.cy.ts +0 -126
  52. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/pages-editor.bdd.md +0 -207
  53. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/pages-editor.cy.ts +0 -211
  54. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/posts-editor.bdd.md +0 -184
  55. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/posts-editor.cy.ts +0 -350
  56. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/public.cy.ts +0 -112
  57. package/templates/contents/themes/starter/tests/cypress/e2e/_utils/selectors/taxonomies.cy.ts +0 -126
@@ -0,0 +1,362 @@
1
+ /**
2
+ * SettingsPOM - Page Object Model for Settings Area
3
+ *
4
+ * Handles settings pages navigation and basic visibility:
5
+ * - Profile settings
6
+ * - Team settings
7
+ * - Billing settings (navigation only - use BillingPOM for detailed billing tests)
8
+ * - API Keys settings
9
+ * - Member management
10
+ *
11
+ * Uses selectors from centralized selectors.ts
12
+ *
13
+ * NOTE: For detailed billing tests (upgrade, invoices, payment methods),
14
+ * use BillingPOM from features/BillingPOM.ts instead.
15
+ */
16
+
17
+ import { BasePOM } from '../core/BasePOM'
18
+ import { cySelector } from '../selectors'
19
+
20
+ export class SettingsPOM extends BasePOM {
21
+ // ============================================
22
+ // FACTORY METHOD
23
+ // ============================================
24
+
25
+ static create(): SettingsPOM {
26
+ return new SettingsPOM()
27
+ }
28
+
29
+ // ============================================
30
+ // SELECTORS
31
+ // ============================================
32
+
33
+ get selectors() {
34
+ return {
35
+ // ============================================
36
+ // LAYOUT (7 selectors)
37
+ // ============================================
38
+ layoutMain: cySelector('settings.layout.main'),
39
+ layoutNav: cySelector('settings.layout.nav'),
40
+ layoutBackToDashboard: cySelector('settings.layout.backToDashboard'),
41
+ layoutHeader: cySelector('settings.layout.header'),
42
+ layoutContentArea: cySelector('settings.layout.contentArea'),
43
+ layoutSidebar: cySelector('settings.layout.sidebar'),
44
+ layoutPageContent: cySelector('settings.layout.pageContent'),
45
+
46
+ // ============================================
47
+ // SIDEBAR (4 selectors)
48
+ // ============================================
49
+ navContainer: cySelector('settings.sidebar.main'),
50
+ sidebarHeader: cySelector('settings.sidebar.header'),
51
+ sidebarNavItems: cySelector('settings.sidebar.navItems'),
52
+ navItem: (section: string) => cySelector('settings.sidebar.navItem', { section }),
53
+
54
+ // ============================================
55
+ // PROFILE (9 selectors)
56
+ // ============================================
57
+ profileContainer: cySelector('settings.profile.container'),
58
+ profileForm: cySelector('settings.profile.form'),
59
+ profileAvatar: cySelector('settings.profile.avatar'),
60
+ profileAvatarUpload: cySelector('settings.profile.avatarUpload'),
61
+ profileFirstName: cySelector('settings.profile.firstName'),
62
+ profileLastName: cySelector('settings.profile.lastName'),
63
+ profileEmail: cySelector('settings.profile.email'),
64
+ profileSubmit: cySelector('settings.profile.submitButton'),
65
+ profileSuccess: cySelector('settings.profile.successMessage'),
66
+
67
+ // ============================================
68
+ // PASSWORD (7 selectors)
69
+ // ============================================
70
+ passwordContainer: cySelector('settings.password.container'),
71
+ passwordForm: cySelector('settings.password.form'),
72
+ passwordCurrent: cySelector('settings.password.currentPassword'),
73
+ passwordNew: cySelector('settings.password.newPassword'),
74
+ passwordConfirm: cySelector('settings.password.confirmPassword'),
75
+ passwordSubmit: cySelector('settings.password.submitButton'),
76
+ passwordSuccess: cySelector('settings.password.successMessage'),
77
+
78
+ // ============================================
79
+ // TEAM (10 selectors)
80
+ // ============================================
81
+ teamContainer: cySelector('settings.team.container'),
82
+ teamName: cySelector('settings.team.name'),
83
+ teamSlug: cySelector('settings.team.slug'),
84
+ teamDescription: cySelector('settings.team.description'),
85
+ teamAvatar: cySelector('settings.team.avatar'),
86
+ teamAvatarUpload: cySelector('settings.team.avatarUpload'),
87
+ teamSubmit: cySelector('settings.team.submitButton'),
88
+ teamDelete: cySelector('settings.team.deleteButton'),
89
+ teamDeleteDialog: cySelector('settings.team.deleteDialog'),
90
+ teamDeleteConfirm: cySelector('settings.team.deleteConfirm'),
91
+
92
+ // ============================================
93
+ // MEMBERS (12 selectors)
94
+ // ============================================
95
+ membersContainer: cySelector('settings.members.container'),
96
+ membersInvite: cySelector('settings.members.inviteButton'),
97
+ membersInviteDialog: cySelector('settings.members.inviteDialog'),
98
+ membersInviteEmail: cySelector('settings.members.inviteEmail'),
99
+ membersInviteRole: cySelector('settings.members.inviteRole'),
100
+ membersInviteSubmit: cySelector('settings.members.inviteSubmit'),
101
+ memberRow: (id: string) => cySelector('settings.members.memberRow', { id }),
102
+ memberRowGeneric: '[data-cy^="member-row-"]',
103
+ memberRole: (id: string) => cySelector('settings.members.memberRole', { id }),
104
+ memberRemove: (id: string) => cySelector('settings.members.memberRemove', { id }),
105
+ membersPendingInvites: cySelector('settings.members.pendingInvites'),
106
+ membersPendingInvite: (id: string) => cySelector('settings.members.pendingInvite', { id }),
107
+ membersCancelInvite: (id: string) => cySelector('settings.members.cancelInvite', { id }),
108
+
109
+ // ============================================
110
+ // BILLING (19 selectors)
111
+ // ============================================
112
+ billingContainer: cySelector('settings.billing.container'),
113
+ billingMain: cySelector('settings.billing.main'),
114
+ billingHeader: cySelector('settings.billing.header'),
115
+ billingCurrentPlan: cySelector('settings.billing.currentPlan'),
116
+ billingUpgrade: cySelector('settings.billing.upgradeButton'),
117
+ billingUpgradePlan: cySelector('settings.billing.upgradePlan'),
118
+ billingCancel: cySelector('settings.billing.cancelButton'),
119
+ billingAddPayment: cySelector('settings.billing.addPayment'),
120
+ billingInvoices: cySelector('settings.billing.invoicesTable'),
121
+ billingInvoicesAlt: cySelector('settings.billing.invoicesTableAlt'),
122
+ billingInvoiceRow: (id: string) => cySelector('settings.billing.invoiceRow', { id }),
123
+ billingInvoicesRow: cySelector('settings.billing.invoicesRow'),
124
+ billingInvoiceDownload: (id: string) => cySelector('settings.billing.invoiceDownload', { id }),
125
+ billingInvoicesLoadMore: cySelector('settings.billing.invoicesLoadMore'),
126
+ billingPaymentMethod: cySelector('settings.billing.paymentMethod'),
127
+ billingPaymentMethodAlt: cySelector('settings.billing.paymentMethodAlt'),
128
+ billingUpdatePayment: cySelector('settings.billing.updatePayment'),
129
+ billingUsage: cySelector('settings.billing.usage'),
130
+ billingUsageDashboard: cySelector('settings.billing.usageDashboard'),
131
+
132
+ // ============================================
133
+ // API KEYS (56 selectors)
134
+ // ============================================
135
+ apiKeysPage: cySelector('settings.apiKeys.page'),
136
+ apiKeysTitle: cySelector('settings.apiKeys.title'),
137
+ apiKeysContainer: cySelector('settings.apiKeys.container'),
138
+ apiKeysCreate: cySelector('settings.apiKeys.createButton'),
139
+ apiKeysCreateDialog: cySelector('settings.apiKeys.createDialog'),
140
+ apiKeysList: cySelector('settings.apiKeys.list'),
141
+ apiKeysSkeleton: cySelector('settings.apiKeys.skeleton'),
142
+ apiKeysEmpty: cySelector('settings.apiKeys.empty'),
143
+ apiKeysEmptyCreate: cySelector('settings.apiKeys.emptyCreateButton'),
144
+ apiKeyName: cySelector('settings.apiKeys.keyName'),
145
+ apiKeyScopes: cySelector('settings.apiKeys.keyScopes'),
146
+ apiKeyScopeOption: (scope: string) => cySelector('settings.apiKeys.scopeOption', { scope }),
147
+ apiKeyCreateSubmit: cySelector('settings.apiKeys.createSubmit'),
148
+ apiKeyRow: (id: string) => cySelector('settings.apiKeys.keyRow', { id }),
149
+ apiKeyRowGeneric: '[data-cy^="api-key-row-"]',
150
+ apiKeyNameById: (id: string) => cySelector('settings.apiKeys.keyName_', { id }),
151
+ apiKeyPrefix: (id: string) => cySelector('settings.apiKeys.keyPrefix', { id }),
152
+ apiKeyCopyPrefix: (id: string) => cySelector('settings.apiKeys.copyPrefix', { id }),
153
+ apiKeyStatus: (id: string) => cySelector('settings.apiKeys.keyStatus', { id }),
154
+ apiKeyStatusBadge: (id: string) => cySelector('settings.apiKeys.statusBadge', { id }),
155
+ apiKeyMenuTrigger: (id: string) => cySelector('settings.apiKeys.menuTrigger', { id }),
156
+ apiKeyMenu: (id: string) => cySelector('settings.apiKeys.menu', { id }),
157
+ apiKeyViewDetails: (id: string) => cySelector('settings.apiKeys.viewDetails', { id }),
158
+ apiKeyToggle: (id: string) => cySelector('settings.apiKeys.toggle', { id }),
159
+ apiKeyRevoke: (id: string) => cySelector('settings.apiKeys.revoke', { id }),
160
+ apiKeyScopes: (id: string) => cySelector('settings.apiKeys.scopes', { id }),
161
+ apiKeyScope: (id: string, scope: string) => cySelector('settings.apiKeys.scope', { id, scope }),
162
+ apiKeyStats: (id: string) => cySelector('settings.apiKeys.stats', { id }),
163
+ apiKeyTotalRequests: (id: string) => cySelector('settings.apiKeys.totalRequests', { id }),
164
+ apiKeyLast24h: (id: string) => cySelector('settings.apiKeys.last24h', { id }),
165
+ apiKeyAvgTime: (id: string) => cySelector('settings.apiKeys.avgTime', { id }),
166
+ apiKeyMetadata: (id: string) => cySelector('settings.apiKeys.metadata', { id }),
167
+ apiKeyCreatedAt: (id: string) => cySelector('settings.apiKeys.createdAt', { id }),
168
+ apiKeyLastUsed: (id: string) => cySelector('settings.apiKeys.lastUsed', { id }),
169
+ apiKeyExpiresAt: (id: string) => cySelector('settings.apiKeys.expiresAt', { id }),
170
+ apiKeysDetailsDialog: cySelector('settings.apiKeys.detailsDialog'),
171
+ apiKeysDetailsTitle: cySelector('settings.apiKeys.detailsTitle'),
172
+ apiKeysDetailsLoading: cySelector('settings.apiKeys.detailsLoading'),
173
+ apiKeysDetailsContent: cySelector('settings.apiKeys.detailsContent'),
174
+ apiKeysDetailsBasicInfo: cySelector('settings.apiKeys.detailsBasicInfo'),
175
+ apiKeysDetailsName: cySelector('settings.apiKeys.detailsName'),
176
+ apiKeysDetailsStatus: cySelector('settings.apiKeys.detailsStatus'),
177
+ apiKeysDetailsStats: cySelector('settings.apiKeys.detailsStats'),
178
+ apiKeysDetailsTotalRequests: cySelector('settings.apiKeys.detailsTotalRequests'),
179
+ apiKeysDetailsLast24h: cySelector('settings.apiKeys.detailsLast24h'),
180
+ apiKeysDetailsLast7d: cySelector('settings.apiKeys.detailsLast7d'),
181
+ apiKeysDetailsLast30d: cySelector('settings.apiKeys.detailsLast30d'),
182
+ apiKeysDetailsAvgTime: cySelector('settings.apiKeys.detailsAvgTime'),
183
+ apiKeysDetailsSuccessRate: cySelector('settings.apiKeys.detailsSuccessRate'),
184
+ apiKeyReveal: (id: string) => cySelector('settings.apiKeys.keyReveal', { id }),
185
+ apiKeyRevokeById: (id: string) => cySelector('settings.apiKeys.keyRevoke', { id }),
186
+ apiKeyRevokeDialog: cySelector('settings.apiKeys.revokeDialog'),
187
+ apiKeyRevokeConfirm: cySelector('settings.apiKeys.revokeConfirm'),
188
+ apiKeyNewDisplay: cySelector('settings.apiKeys.newKeyDisplay'),
189
+ apiKeyCopyKey: cySelector('settings.apiKeys.copyKey'),
190
+ apiKeysDialogFooter: cySelector('settings.apiKeys.dialogFooter'),
191
+ }
192
+ }
193
+
194
+ // ============================================
195
+ // NAVIGATION
196
+ // ============================================
197
+
198
+ /**
199
+ * Navigate to settings home
200
+ */
201
+ visitSettings() {
202
+ cy.visit('/dashboard/settings', { timeout: 60000 })
203
+ return this
204
+ }
205
+
206
+ /**
207
+ * Navigate to profile settings
208
+ */
209
+ visitProfile() {
210
+ cy.visit('/dashboard/settings/profile', { timeout: 60000 })
211
+ return this
212
+ }
213
+
214
+ /**
215
+ * Navigate to team settings
216
+ */
217
+ visitTeam() {
218
+ cy.visit('/dashboard/settings/team', { timeout: 60000 })
219
+ return this
220
+ }
221
+
222
+ /**
223
+ * Navigate to billing settings
224
+ */
225
+ visitBilling() {
226
+ cy.visit('/dashboard/settings/billing', { timeout: 60000 })
227
+ return this
228
+ }
229
+
230
+ /**
231
+ * Navigate to members settings
232
+ */
233
+ visitMembers() {
234
+ cy.visit('/dashboard/settings/members', { timeout: 60000 })
235
+ return this
236
+ }
237
+
238
+ /**
239
+ * Navigate to API keys settings
240
+ */
241
+ visitApiKeys() {
242
+ cy.visit('/dashboard/settings/api-keys', { timeout: 60000 })
243
+ return this
244
+ }
245
+
246
+ /**
247
+ * Navigate to password settings
248
+ */
249
+ visitPassword() {
250
+ cy.visit('/dashboard/settings/password', { timeout: 60000 })
251
+ return this
252
+ }
253
+
254
+ /**
255
+ * Click on a settings nav item
256
+ */
257
+ clickNavItem(section: string) {
258
+ cy.get(this.selectors.navItem(section)).click()
259
+ return this
260
+ }
261
+
262
+ // ============================================
263
+ // ASSERTIONS
264
+ // ============================================
265
+
266
+ /**
267
+ * Assert settings page is visible
268
+ */
269
+ assertSettingsVisible() {
270
+ cy.url().should('include', '/settings')
271
+ return this
272
+ }
273
+
274
+ /**
275
+ * Assert profile settings is visible
276
+ */
277
+ assertProfileVisible() {
278
+ cy.get(this.selectors.profileContainer).should('be.visible')
279
+ return this
280
+ }
281
+
282
+ /**
283
+ * Assert team settings is visible
284
+ */
285
+ assertTeamVisible() {
286
+ cy.get(this.selectors.teamContainer).should('be.visible')
287
+ return this
288
+ }
289
+
290
+ /**
291
+ * Assert billing settings is visible
292
+ */
293
+ assertBillingVisible() {
294
+ cy.get(this.selectors.billingContainer).should('be.visible')
295
+ return this
296
+ }
297
+
298
+ /**
299
+ * Assert nav item is visible
300
+ */
301
+ assertNavItemVisible(section: string) {
302
+ cy.get(this.selectors.navItem(section)).should('be.visible')
303
+ return this
304
+ }
305
+
306
+ /**
307
+ * Assert nav item is NOT visible (restricted)
308
+ */
309
+ assertNavItemNotVisible(section: string) {
310
+ cy.get(this.selectors.navItem(section)).should('not.exist')
311
+ return this
312
+ }
313
+
314
+ /**
315
+ * Assert members container is visible
316
+ */
317
+ assertMembersVisible() {
318
+ cy.get(this.selectors.membersContainer).should('be.visible')
319
+ return this
320
+ }
321
+
322
+ /**
323
+ * Assert API keys container is visible
324
+ */
325
+ assertApiKeysVisible() {
326
+ cy.get(this.selectors.apiKeysContainer).should('be.visible')
327
+ return this
328
+ }
329
+
330
+ // ============================================
331
+ // WAITS
332
+ // ============================================
333
+
334
+ /**
335
+ * Wait for settings page to load
336
+ */
337
+ waitForSettings() {
338
+ cy.url().should('include', '/settings')
339
+ cy.get(this.selectors.navContainer, { timeout: 15000 }).should('be.visible')
340
+ return this
341
+ }
342
+
343
+ /**
344
+ * Wait for billing page to load
345
+ */
346
+ waitForBilling() {
347
+ cy.url().should('include', '/settings/billing')
348
+ cy.get(this.selectors.billingContainer, { timeout: 15000 }).should('be.visible')
349
+ return this
350
+ }
351
+
352
+ /**
353
+ * Wait for team page to load
354
+ */
355
+ waitForTeam() {
356
+ cy.url().should('include', '/settings/team')
357
+ cy.get(this.selectors.teamContainer, { timeout: 15000 }).should('be.visible')
358
+ return this
359
+ }
360
+ }
361
+
362
+ export default SettingsPOM
@@ -0,0 +1,331 @@
1
+ /**
2
+ * SuperadminPOM - Page Object Model for Superadmin Area
3
+ *
4
+ * Handles Superadmin Panel pages:
5
+ * - Dashboard with system stats (/superadmin)
6
+ * - All users management (/superadmin/users)
7
+ * - All teams management (/superadmin/teams)
8
+ * - Subscriptions overview (/superadmin/subscriptions)
9
+ * - System configuration (/superadmin/system)
10
+ *
11
+ * Uses selectors from centralized selectors.ts
12
+ *
13
+ * NOTE: For /superadmin/team-roles (permissions matrix), use SuperadminTeamRolesPOM instead.
14
+ */
15
+
16
+ import { BasePOM } from '../core/BasePOM'
17
+ import { cySelector } from '../selectors'
18
+
19
+ export class SuperadminPOM extends BasePOM {
20
+ // ============================================
21
+ // FACTORY METHOD
22
+ // ============================================
23
+
24
+ static create(): SuperadminPOM {
25
+ return new SuperadminPOM()
26
+ }
27
+
28
+ // ============================================
29
+ // SELECTORS
30
+ // ============================================
31
+
32
+ get selectors() {
33
+ return {
34
+ // Navigation
35
+ navContainer: cySelector('superadmin.container'),
36
+ navDashboard: cySelector('superadmin.navigation.dashboard'),
37
+ navUsers: cySelector('superadmin.navigation.users'),
38
+ navTeams: cySelector('superadmin.navigation.teams'),
39
+ navTeamRoles: cySelector('superadmin.navigation.teamRoles'),
40
+ navSubscriptions: cySelector('superadmin.navigation.subscriptions'),
41
+ exitToDashboard: cySelector('superadmin.navigation.exitToDashboard'),
42
+
43
+ // Dashboard
44
+ dashboardContainer: cySelector('superadmin.dashboard.container'),
45
+
46
+ // Users
47
+ usersContainer: cySelector('superadmin.users.container'),
48
+ usersTable: cySelector('superadmin.users.table'),
49
+ usersSearch: cySelector('superadmin.users.search'),
50
+ userRow: (id: string) => cySelector('superadmin.users.row', { id }),
51
+ userView: (id: string) => cySelector('superadmin.users.viewButton', { id }),
52
+ userEdit: (id: string) => cySelector('superadmin.users.editButton', { id }),
53
+ userBan: (id: string) => cySelector('superadmin.users.banButton', { id }),
54
+ userDelete: (id: string) => cySelector('superadmin.users.deleteButton', { id }),
55
+ userImpersonate: (id: string) => cySelector('superadmin.users.impersonateButton', { id }),
56
+
57
+ // Teams
58
+ teamsContainer: cySelector('superadmin.teams.container'),
59
+ teamsTable: cySelector('superadmin.teams.table'),
60
+ teamsSearch: cySelector('superadmin.teams.search'),
61
+ teamRow: (id: string) => cySelector('superadmin.teams.row', { id }),
62
+ teamActionsButton: (id: string) => cySelector('superadmin.teams.actionsButton', { id }),
63
+ teamView: (id: string) => cySelector('superadmin.teams.viewButton', { id }),
64
+ teamEdit: (id: string) => cySelector('superadmin.teams.editButton', { id }),
65
+ teamDelete: (id: string) => cySelector('superadmin.teams.deleteButton', { id }),
66
+
67
+ // Pagination (shared across superadmin pages)
68
+ paginationPageSize: cySelector('superadmin.pagination.pageSize'),
69
+ paginationFirst: cySelector('superadmin.pagination.first'),
70
+ paginationPrev: cySelector('superadmin.pagination.prev'),
71
+ paginationNext: cySelector('superadmin.pagination.next'),
72
+ paginationLast: cySelector('superadmin.pagination.last'),
73
+
74
+ // Permissions (team-roles page)
75
+ permissionRow: (permission: string) => cySelector('superadmin.permissions.row', { permission: permission.replace(/\./g, '-') }),
76
+
77
+ // Subscriptions
78
+ subscriptionsContainer: cySelector('superadmin.subscriptions.container'),
79
+ subscriptionsMrr: cySelector('superadmin.subscriptions.mrr'),
80
+ subscriptionsPlanDistribution: cySelector('superadmin.subscriptions.planDistribution'),
81
+ subscriptionsPlanCount: (plan: string) => cySelector('superadmin.subscriptions.planCount', { plan }),
82
+ subscriptionsActiveCount: cySelector('superadmin.subscriptions.activeCount'),
83
+ }
84
+ }
85
+
86
+ // ============================================
87
+ // NAVIGATION
88
+ // ============================================
89
+
90
+ /**
91
+ * Navigate to Superadmin dashboard
92
+ */
93
+ visitDashboard() {
94
+ cy.visit('/superadmin', { timeout: 60000 })
95
+ return this
96
+ }
97
+
98
+ /**
99
+ * Navigate to all users page
100
+ */
101
+ visitUsers() {
102
+ cy.visit('/superadmin/users', { timeout: 60000 })
103
+ return this
104
+ }
105
+
106
+ /**
107
+ * Navigate to all teams page
108
+ */
109
+ visitTeams() {
110
+ cy.visit('/superadmin/teams', { timeout: 60000 })
111
+ return this
112
+ }
113
+
114
+ /**
115
+ * Navigate to subscriptions overview
116
+ */
117
+ visitSubscriptions() {
118
+ cy.visit('/superadmin/subscriptions', { timeout: 60000 })
119
+ return this
120
+ }
121
+
122
+ /**
123
+ * Click nav link to dashboard
124
+ */
125
+ clickNavDashboard() {
126
+ cy.get(this.selectors.navDashboard).click()
127
+ return this
128
+ }
129
+
130
+ /**
131
+ * Click nav link to users
132
+ */
133
+ clickNavUsers() {
134
+ cy.get(this.selectors.navUsers).click()
135
+ return this
136
+ }
137
+
138
+ /**
139
+ * Click nav link to teams
140
+ */
141
+ clickNavTeams() {
142
+ cy.get(this.selectors.navTeams).click()
143
+ return this
144
+ }
145
+
146
+ // ============================================
147
+ // USERS ACTIONS
148
+ // ============================================
149
+
150
+ /**
151
+ * Search users
152
+ */
153
+ searchUsers(query: string) {
154
+ cy.get(this.selectors.usersSearch).clear().type(query)
155
+ return this
156
+ }
157
+
158
+ /**
159
+ * View user details
160
+ */
161
+ viewUser(id: string) {
162
+ cy.get(this.selectors.userView(id)).click()
163
+ return this
164
+ }
165
+
166
+ /**
167
+ * Ban a user
168
+ */
169
+ banUser(id: string) {
170
+ cy.get(this.selectors.userBan(id)).click()
171
+ return this
172
+ }
173
+
174
+ /**
175
+ * Impersonate a user
176
+ */
177
+ impersonateUser(id: string) {
178
+ cy.get(this.selectors.userImpersonate(id)).click()
179
+ return this
180
+ }
181
+
182
+ // ============================================
183
+ // TEAMS ACTIONS
184
+ // ============================================
185
+
186
+ /**
187
+ * Search teams
188
+ */
189
+ searchTeams(query: string) {
190
+ cy.get(this.selectors.teamsSearch).clear().type(query)
191
+ return this
192
+ }
193
+
194
+ /**
195
+ * View team details
196
+ */
197
+ viewTeam(id: string) {
198
+ cy.get(this.selectors.teamView(id)).click()
199
+ return this
200
+ }
201
+
202
+ // ============================================
203
+ // ASSERTIONS
204
+ // ============================================
205
+
206
+ /**
207
+ * Assert on Superadmin area
208
+ */
209
+ assertOnSuperadmin() {
210
+ cy.url().should('include', '/superadmin')
211
+ return this
212
+ }
213
+
214
+ /**
215
+ * Assert dashboard is visible
216
+ */
217
+ assertDashboardVisible() {
218
+ cy.get(this.selectors.dashboardContainer).should('be.visible')
219
+ return this
220
+ }
221
+
222
+ /**
223
+ * Assert stats cards are visible
224
+ */
225
+ assertStatsVisible() {
226
+ cy.get(this.selectors.statsUsers).should('be.visible')
227
+ cy.get(this.selectors.statsTeams).should('be.visible')
228
+ return this
229
+ }
230
+
231
+ /**
232
+ * Assert users page is visible
233
+ */
234
+ assertUsersVisible() {
235
+ cy.get(this.selectors.usersContainer).should('be.visible')
236
+ return this
237
+ }
238
+
239
+ /**
240
+ * Assert users table is visible
241
+ */
242
+ assertUsersTableVisible() {
243
+ cy.get(this.selectors.usersTable).should('be.visible')
244
+ return this
245
+ }
246
+
247
+ /**
248
+ * Assert teams page is visible
249
+ */
250
+ assertTeamsVisible() {
251
+ cy.get(this.selectors.teamsContainer).should('be.visible')
252
+ return this
253
+ }
254
+
255
+ /**
256
+ * Assert teams table is visible
257
+ */
258
+ assertTeamsTableVisible() {
259
+ cy.get(this.selectors.teamsTable).should('be.visible')
260
+ return this
261
+ }
262
+
263
+ /**
264
+ * Assert nav items are visible
265
+ */
266
+ assertNavVisible() {
267
+ cy.get(this.selectors.navContainer).should('be.visible')
268
+ return this
269
+ }
270
+
271
+ /**
272
+ * Assert subscriptions page is visible
273
+ */
274
+ assertSubscriptionsVisible() {
275
+ cy.get(this.selectors.subscriptionsContainer).should('be.visible')
276
+ return this
277
+ }
278
+
279
+ /**
280
+ * Assert access denied (redirect from superadmin)
281
+ */
282
+ assertAccessDenied() {
283
+ cy.url().should('not.include', '/superadmin')
284
+ cy.url().should('satisfy', (url: string) => {
285
+ return url.includes('/dashboard') || url.includes('error=access_denied')
286
+ })
287
+ return this
288
+ }
289
+
290
+ // ============================================
291
+ // WAITS
292
+ // ============================================
293
+
294
+ /**
295
+ * Wait for Superadmin dashboard to load
296
+ */
297
+ waitForDashboard() {
298
+ cy.url().should('include', '/superadmin')
299
+ cy.get(this.selectors.dashboardContainer, { timeout: 15000 }).should('be.visible')
300
+ return this
301
+ }
302
+
303
+ /**
304
+ * Wait for users page to load
305
+ */
306
+ waitForUsers() {
307
+ cy.url().should('include', '/superadmin/users')
308
+ cy.get(this.selectors.usersContainer, { timeout: 15000 }).should('be.visible')
309
+ return this
310
+ }
311
+
312
+ /**
313
+ * Wait for teams page to load
314
+ */
315
+ waitForTeams() {
316
+ cy.url().should('include', '/superadmin/teams')
317
+ cy.get(this.selectors.teamsContainer, { timeout: 15000 }).should('be.visible')
318
+ return this
319
+ }
320
+
321
+ /**
322
+ * Wait for subscriptions page to load
323
+ */
324
+ waitForSubscriptions() {
325
+ cy.url().should('include', '/superadmin/subscriptions')
326
+ cy.get(this.selectors.subscriptionsContainer, { timeout: 15000 }).should('be.visible')
327
+ return this
328
+ }
329
+ }
330
+
331
+ export default SuperadminPOM