@nextsparkjs/theme-default 0.1.0-beta.44 → 0.1.0-beta.45

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 (48) hide show
  1. package/components/ai-chat/ChatPanel.tsx +7 -7
  2. package/components/ai-chat/Message.tsx +2 -2
  3. package/components/ai-chat/MessageInput.tsx +3 -3
  4. package/components/ai-chat/MessageList.tsx +3 -3
  5. package/components/ai-chat/TypingIndicator.tsx +2 -2
  6. package/entities/customers/api/docs.md +107 -0
  7. package/entities/customers/api/presets.ts +80 -0
  8. package/entities/pages/api/docs.md +114 -0
  9. package/entities/pages/api/presets.ts +72 -0
  10. package/entities/posts/api/docs.md +120 -0
  11. package/entities/posts/api/presets.ts +74 -0
  12. package/entities/tasks/api/docs.md +126 -0
  13. package/entities/tasks/api/presets.ts +84 -0
  14. package/lib/selectors.ts +2 -2
  15. package/messages/de/admin.json +45 -0
  16. package/messages/en/admin.json +45 -0
  17. package/messages/es/admin.json +45 -0
  18. package/messages/fr/admin.json +45 -0
  19. package/messages/it/admin.json +45 -0
  20. package/messages/pt/admin.json +45 -0
  21. package/package.json +3 -3
  22. package/styles/globals.css +24 -0
  23. package/tests/cypress/e2e/_utils/selectors/block-editor.bdd.md +491 -0
  24. package/tests/cypress/e2e/_utils/selectors/block-editor.cy.ts +475 -0
  25. package/tests/cypress/e2e/_utils/selectors/dashboard-container.cy.ts +52 -0
  26. package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.cy.ts +14 -14
  27. package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.cy.ts +3 -3
  28. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.bdd.md +38 -73
  29. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.cy.ts +21 -42
  30. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.bdd.md +117 -38
  31. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.cy.ts +35 -12
  32. package/tests/cypress/e2e/_utils/selectors/settings-layout.bdd.md +50 -59
  33. package/tests/cypress/e2e/_utils/selectors/settings-layout.cy.ts +15 -23
  34. package/tests/cypress/e2e/_utils/selectors/tasks.bdd.md +395 -155
  35. package/tests/cypress/e2e/_utils/selectors/tasks.cy.ts +795 -174
  36. package/tests/cypress/e2e/api/_core/teams/teams-security.cy.ts +415 -0
  37. package/tests/cypress/e2e/uat/_core/teams/inline-edit.cy.ts +278 -0
  38. package/tests/cypress/src/core/BlockEditorBasePOM.ts +269 -99
  39. package/tests/cypress/src/core/DashboardEntityPOM.ts +1 -1
  40. package/tests/cypress/src/features/DashboardPOM.ts +49 -28
  41. package/tests/cypress/src/features/PageBuilderPOM.ts +20 -0
  42. package/tests/cypress/src/features/SettingsPOM.ts +511 -166
  43. package/tests/cypress/src/features/SuperadminPOM.ts +679 -159
  44. package/tests/cypress/src/features/index.ts +10 -10
  45. package/tests/cypress/e2e/_utils/selectors/pages-editor.bdd.md +0 -207
  46. package/tests/cypress/e2e/_utils/selectors/pages-editor.cy.ts +0 -211
  47. package/tests/cypress/e2e/_utils/selectors/posts-editor.bdd.md +0 -184
  48. package/tests/cypress/e2e/_utils/selectors/posts-editor.cy.ts +0 -350
@@ -1,17 +1,18 @@
1
1
  /**
2
2
  * SettingsPOM - Page Object Model for Settings Area
3
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
4
+ * Handles navigation and interactions for all 9 settings sections:
5
+ * 1. Sidebar - Navigation and layout structure
6
+ * 2. Overview - Settings home with quick links
7
+ * 3. Profile - User profile editing
8
+ * 4. Password - Password change
9
+ * 5. Security - 2FA, login alerts, active sessions
10
+ * 6. Notifications - Notification preferences
11
+ * 7. API Keys - API key management
12
+ * 8. Billing - Plans, invoices, payment methods
13
+ * 9. Teams - Team config, members, team switching
10
14
  *
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
+ * Uses selectors from centralized selectors.ts via cySelector()
15
16
  */
16
17
 
17
18
  import { BasePOM } from '../core/BasePOM'
@@ -33,170 +34,262 @@ export class SettingsPOM extends BasePOM {
33
34
  get selectors() {
34
35
  return {
35
36
  // ============================================
36
- // LAYOUT (7 selectors)
37
+ // 1. SIDEBAR (11 selectors)
37
38
  // ============================================
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'),
39
+ sidebarContainer: cySelector('settings.sidebar.container'),
40
+ sidebarHeader: cySelector('settings.sidebar.header'),
41
+ sidebarBackButton: cySelector('settings.sidebar.backButton'),
42
+ sidebarNavContainer: cySelector('settings.sidebar.nav.container'),
43
+ sidebarNavItems: cySelector('settings.sidebar.nav.items'),
44
+ sidebarNavItem: (section: string) => cySelector('settings.sidebar.nav.item', { section }),
45
+ // Layout elements (absorbed from layout.*)
46
+ layoutMain: cySelector('settings.sidebar.layout.main'),
47
+ layoutHeader: cySelector('settings.sidebar.layout.header'),
48
+ layoutContentArea: cySelector('settings.sidebar.layout.contentArea'),
49
+ layoutPageContent: cySelector('settings.sidebar.layout.pageContent'),
45
50
 
46
51
  // ============================================
47
- // SIDEBAR (4 selectors)
52
+ // 2. OVERVIEW (2 selectors)
48
53
  // ============================================
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 }),
54
+ overviewContainer: cySelector('settings.overview.container'),
55
+ overviewCard: (key: string) => cySelector('settings.overview.card', { key }),
53
56
 
54
57
  // ============================================
55
- // PROFILE (9 selectors)
58
+ // 3. PROFILE (11 selectors)
56
59
  // ============================================
57
60
  profileContainer: cySelector('settings.profile.container'),
58
61
  profileForm: cySelector('settings.profile.form'),
59
- profileAvatar: cySelector('settings.profile.avatar'),
60
- profileAvatarUpload: cySelector('settings.profile.avatarUpload'),
62
+ profileAvatarContainer: cySelector('settings.profile.avatar.container'),
63
+ profileAvatarUpload: cySelector('settings.profile.avatar.upload'),
61
64
  profileFirstName: cySelector('settings.profile.firstName'),
62
65
  profileLastName: cySelector('settings.profile.lastName'),
63
66
  profileEmail: cySelector('settings.profile.email'),
67
+ profileCountry: cySelector('settings.profile.country'),
68
+ profileTimezone: cySelector('settings.profile.timezone'),
69
+ profileLocale: cySelector('settings.profile.locale'),
64
70
  profileSubmit: cySelector('settings.profile.submitButton'),
65
71
  profileSuccess: cySelector('settings.profile.successMessage'),
72
+ profileDeleteAccountButton: cySelector('settings.profile.deleteAccount.button'),
73
+ profileDeleteAccountDialog: cySelector('settings.profile.deleteAccount.dialog'),
74
+ profileDeleteAccountConfirm: cySelector('settings.profile.deleteAccount.confirm'),
66
75
 
67
76
  // ============================================
68
- // PASSWORD (7 selectors)
77
+ // 4. PASSWORD (8 selectors)
69
78
  // ============================================
70
79
  passwordContainer: cySelector('settings.password.container'),
71
80
  passwordForm: cySelector('settings.password.form'),
72
81
  passwordCurrent: cySelector('settings.password.currentPassword'),
73
82
  passwordNew: cySelector('settings.password.newPassword'),
74
83
  passwordConfirm: cySelector('settings.password.confirmPassword'),
84
+ passwordRevokeOtherSessions: cySelector('settings.password.revokeOtherSessions'),
75
85
  passwordSubmit: cySelector('settings.password.submitButton'),
76
86
  passwordSuccess: cySelector('settings.password.successMessage'),
77
87
 
78
88
  // ============================================
79
- // TEAM (10 selectors)
89
+ // 5. SECURITY (25 selectors)
80
90
  // ============================================
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
+ securityContainer: cySelector('settings.security.container'),
92
+ securityHeader: cySelector('settings.security.header'),
93
+ // Two-Factor Authentication
94
+ security2faContainer: cySelector('settings.security.twoFactor.container'),
95
+ security2faToggle: cySelector('settings.security.twoFactor.toggle'),
96
+ security2faStatus: cySelector('settings.security.twoFactor.status'),
97
+ security2faSetupButton: cySelector('settings.security.twoFactor.setupButton'),
98
+ security2faDisableButton: cySelector('settings.security.twoFactor.disableButton'),
99
+ security2faDialogContainer: cySelector('settings.security.twoFactor.setupDialog.container'),
100
+ security2faQrCode: cySelector('settings.security.twoFactor.setupDialog.qrCode'),
101
+ security2faSecretKey: cySelector('settings.security.twoFactor.setupDialog.secretKey'),
102
+ security2faVerifyInput: cySelector('settings.security.twoFactor.setupDialog.verifyInput'),
103
+ security2faConfirmButton: cySelector('settings.security.twoFactor.setupDialog.confirmButton'),
104
+ // Login Alerts
105
+ securityLoginAlertsToggle: cySelector('settings.security.loginAlerts.toggle'),
106
+ securityLoginAlertsStatus: cySelector('settings.security.loginAlerts.status'),
107
+ // Active Sessions
108
+ securitySessionsContainer: cySelector('settings.security.sessions.container'),
109
+ securitySessionsList: cySelector('settings.security.sessions.list'),
110
+ securitySessionRow: (id: string) => cySelector('settings.security.sessions.row.container', { id }),
111
+ securitySessionDevice: (id: string) => cySelector('settings.security.sessions.row.device', { id }),
112
+ securitySessionBrowser: (id: string) => cySelector('settings.security.sessions.row.browser', { id }),
113
+ securitySessionLocation: (id: string) => cySelector('settings.security.sessions.row.location', { id }),
114
+ securitySessionLastActive: (id: string) => cySelector('settings.security.sessions.row.lastActive', { id }),
115
+ securitySessionCurrentBadge: (id: string) => cySelector('settings.security.sessions.row.currentBadge', { id }),
116
+ securitySessionRevokeButton: (id: string) => cySelector('settings.security.sessions.row.revokeButton', { id }),
117
+ securitySessionsRevokeAll: cySelector('settings.security.sessions.revokeAllButton'),
118
+ securitySuccess: cySelector('settings.security.successMessage'),
91
119
 
92
120
  // ============================================
93
- // MEMBERS (12 selectors)
121
+ // TEAM INLINE EDIT (14 selectors)
94
122
  // ============================================
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 }),
123
+ teamEditNameValue: cySelector('teams.edit.name.value'),
124
+ teamEditNameEditIcon: cySelector('teams.edit.name.editIcon'),
125
+ teamEditNameInput: cySelector('teams.edit.name.input'),
126
+ teamEditNameSaveIcon: cySelector('teams.edit.name.saveIcon'),
127
+ teamEditNameCancelIcon: cySelector('teams.edit.name.cancelIcon'),
128
+ teamEditNameError: cySelector('teams.edit.name.error'),
129
+ teamEditNameSuccess: cySelector('teams.edit.name.success'),
130
+ teamEditDescriptionValue: cySelector('teams.edit.description.value'),
131
+ teamEditDescriptionEditIcon: cySelector('teams.edit.description.editIcon'),
132
+ teamEditDescriptionTextarea: cySelector('teams.edit.description.textarea'),
133
+ teamEditDescriptionSaveIcon: cySelector('teams.edit.description.saveIcon'),
134
+ teamEditDescriptionCancelIcon: cySelector('teams.edit.description.cancelIcon'),
135
+ teamEditDescriptionError: cySelector('teams.edit.description.error'),
136
+ teamEditDescriptionSuccess: cySelector('teams.edit.description.success'),
108
137
 
109
138
  // ============================================
110
- // BILLING (19 selectors)
139
+ // 6. NOTIFICATIONS (7 selectors)
111
140
  // ============================================
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'),
141
+ notificationsContainer: cySelector('settings.notifications.container'),
142
+ notificationsMasterToggle: cySelector('settings.notifications.masterToggle'),
143
+ notificationsCategoryContainer: (category: string) => cySelector('settings.notifications.category.container', { category }),
144
+ notificationsCategoryEmail: (category: string) => cySelector('settings.notifications.category.emailToggle', { category }),
145
+ notificationsCategoryPush: (category: string) => cySelector('settings.notifications.category.pushToggle', { category }),
146
+ notificationsSubmit: cySelector('settings.notifications.submitButton'),
147
+ notificationsSuccess: cySelector('settings.notifications.successMessage'),
131
148
 
132
149
  // ============================================
133
- // API KEYS (56 selectors)
150
+ // 7. API KEYS (40 selectors)
134
151
  // ============================================
135
- apiKeysPage: cySelector('settings.apiKeys.page'),
136
- apiKeysTitle: cySelector('settings.apiKeys.title'),
137
152
  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 }),
153
+ apiKeysHeader: cySelector('settings.apiKeys.header'),
154
+ apiKeysCreateButton: cySelector('settings.apiKeys.createButton'),
155
+ // List
156
+ apiKeysListContainer: cySelector('settings.apiKeys.list.container'),
157
+ apiKeysListEmpty: cySelector('settings.apiKeys.list.empty'),
158
+ apiKeysListSkeleton: cySelector('settings.apiKeys.list.skeleton'),
159
+ // Row (parametric)
160
+ apiKeyRowContainer: (id: string) => cySelector('settings.apiKeys.row.container', { id }),
149
161
  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'),
162
+ apiKeyRowName: (id: string) => cySelector('settings.apiKeys.row.name', { id }),
163
+ apiKeyRowPrefix: (id: string) => cySelector('settings.apiKeys.row.prefix', { id }),
164
+ apiKeyRowCopyPrefix: (id: string) => cySelector('settings.apiKeys.row.copyPrefix', { id }),
165
+ apiKeyRowStatus: (id: string) => cySelector('settings.apiKeys.row.status', { id }),
166
+ apiKeyRowScopes: (id: string) => cySelector('settings.apiKeys.row.scopes', { id }),
167
+ apiKeyRowScope: (id: string, scope: string) => cySelector('settings.apiKeys.row.scope', { id, scope }),
168
+ // Row Stats
169
+ apiKeyRowStatsContainer: (id: string) => cySelector('settings.apiKeys.row.stats.container', { id }),
170
+ apiKeyRowStatsTotalRequests: (id: string) => cySelector('settings.apiKeys.row.stats.totalRequests', { id }),
171
+ apiKeyRowStatsLast24h: (id: string) => cySelector('settings.apiKeys.row.stats.last24h', { id }),
172
+ apiKeyRowStatsAvgTime: (id: string) => cySelector('settings.apiKeys.row.stats.avgTime', { id }),
173
+ // Row Metadata
174
+ apiKeyRowMetadataContainer: (id: string) => cySelector('settings.apiKeys.row.metadata.container', { id }),
175
+ apiKeyRowMetadataCreatedAt: (id: string) => cySelector('settings.apiKeys.row.metadata.createdAt', { id }),
176
+ apiKeyRowMetadataLastUsed: (id: string) => cySelector('settings.apiKeys.row.metadata.lastUsed', { id }),
177
+ apiKeyRowMetadataExpiresAt: (id: string) => cySelector('settings.apiKeys.row.metadata.expiresAt', { id }),
178
+ // Row Menu
179
+ apiKeyRowMenuTrigger: (id: string) => cySelector('settings.apiKeys.row.menu.trigger', { id }),
180
+ apiKeyRowMenuContent: (id: string) => cySelector('settings.apiKeys.row.menu.content', { id }),
181
+ apiKeyRowMenuViewDetails: (id: string) => cySelector('settings.apiKeys.row.menu.viewDetails', { id }),
182
+ apiKeyRowMenuToggle: (id: string) => cySelector('settings.apiKeys.row.menu.toggle', { id }),
183
+ apiKeyRowMenuRevoke: (id: string) => cySelector('settings.apiKeys.row.menu.revoke', { id }),
184
+ // Create Dialog
185
+ apiKeysCreateDialogContainer: cySelector('settings.apiKeys.createDialog.container'),
186
+ apiKeysCreateDialogNameInput: cySelector('settings.apiKeys.createDialog.nameInput'),
187
+ apiKeysCreateDialogScopesContainer: cySelector('settings.apiKeys.createDialog.scopesContainer'),
188
+ apiKeysCreateDialogScopeOption: (scope: string) => cySelector('settings.apiKeys.createDialog.scopeOption', { scope }),
189
+ apiKeysCreateDialogSubmit: cySelector('settings.apiKeys.createDialog.submitButton'),
190
+ apiKeysCreateDialogFooter: cySelector('settings.apiKeys.createDialog.footer'),
191
+ // Details Dialog
192
+ apiKeysDetailsDialogContainer: cySelector('settings.apiKeys.detailsDialog.container'),
193
+ apiKeysDetailsDialogTitle: cySelector('settings.apiKeys.detailsDialog.title'),
194
+ apiKeysDetailsDialogLoading: cySelector('settings.apiKeys.detailsDialog.loading'),
195
+ apiKeysDetailsDialogContent: cySelector('settings.apiKeys.detailsDialog.content'),
196
+ apiKeysDetailsDialogBasicInfo: cySelector('settings.apiKeys.detailsDialog.basicInfo'),
197
+ apiKeysDetailsDialogStats: cySelector('settings.apiKeys.detailsDialog.stats'),
198
+ // New Key Display
199
+ apiKeysNewKeyDisplayContainer: cySelector('settings.apiKeys.newKeyDisplay.container'),
200
+ apiKeysNewKeyDisplayCopyButton: cySelector('settings.apiKeys.newKeyDisplay.copyButton'),
201
+ // Revoke Dialog
202
+ apiKeysRevokeDialogContainer: cySelector('settings.apiKeys.revokeDialog.container'),
203
+ apiKeysRevokeDialogConfirm: cySelector('settings.apiKeys.revokeDialog.confirmButton'),
204
+
205
+ // ============================================
206
+ // 8. BILLING (15 selectors)
207
+ // ============================================
208
+ billingContainer: cySelector('settings.billing.container'),
209
+ billingHeader: cySelector('settings.billing.header'),
210
+ // Current Plan
211
+ billingCurrentPlanContainer: cySelector('settings.billing.currentPlan.container'),
212
+ billingCurrentPlanName: cySelector('settings.billing.currentPlan.name'),
213
+ billingCurrentPlanPrice: cySelector('settings.billing.currentPlan.price'),
214
+ billingCurrentPlanFeatures: cySelector('settings.billing.currentPlan.features'),
215
+ billingCurrentPlanUpgrade: cySelector('settings.billing.currentPlan.upgradeButton'),
216
+ billingCurrentPlanCancel: cySelector('settings.billing.currentPlan.cancelButton'),
217
+ // Invoices
218
+ billingInvoicesContainer: cySelector('settings.billing.invoices.container'),
219
+ billingInvoicesTable: cySelector('settings.billing.invoices.table'),
220
+ billingInvoicesRow: (id: string) => cySelector('settings.billing.invoices.row', { id }),
221
+ billingInvoicesStatusBadge: (id: string) => cySelector('settings.billing.invoices.statusBadge', { id }),
222
+ billingInvoicesDownload: (id: string) => cySelector('settings.billing.invoices.downloadButton', { id }),
223
+ billingInvoicesLoadMore: cySelector('settings.billing.invoices.loadMoreButton'),
224
+ // Payment Method
225
+ billingPaymentMethodContainer: cySelector('settings.billing.paymentMethod.container'),
226
+ billingPaymentMethodCard: cySelector('settings.billing.paymentMethod.card'),
227
+ billingPaymentMethodAdd: cySelector('settings.billing.paymentMethod.addButton'),
228
+ billingPaymentMethodUpdate: cySelector('settings.billing.paymentMethod.updateButton'),
229
+ // Usage
230
+ billingUsageContainer: cySelector('settings.billing.usage.container'),
231
+ billingUsageDashboard: cySelector('settings.billing.usage.dashboard'),
232
+ billingUsageMetric: (slug: string) => cySelector('settings.billing.usage.metric', { slug }),
233
+ // Pricing
234
+ billingPricingTable: cySelector('settings.billing.pricing.table'),
235
+ billingPricingPlan: (slug: string) => cySelector('settings.billing.pricing.plan', { slug }),
236
+ billingPricingSelect: (slug: string) => cySelector('settings.billing.pricing.selectButton', { slug }),
237
+ // Features
238
+ billingFeaturesPlaceholder: (feature: string) => cySelector('settings.billing.features.placeholder', { feature }),
239
+ billingFeaturesContent: (feature: string) => cySelector('settings.billing.features.content', { feature }),
240
+ billingFeaturesUpgrade: cySelector('settings.billing.features.upgradeButton'),
241
+
242
+ // ============================================
243
+ // 9. TEAMS (25 selectors)
244
+ // ============================================
245
+ teamsContainer: cySelector('settings.teams.container'),
246
+ teamsHeader: cySelector('settings.teams.header'),
247
+ teamsLoading: cySelector('settings.teams.loading'),
248
+ teamsSingleUserMode: cySelector('settings.teams.singleUserMode'),
249
+ // Current Team (ex team.*)
250
+ teamsCurrentContainer: cySelector('settings.teams.current.container'),
251
+ teamsCurrentForm: cySelector('settings.teams.current.form'),
252
+ teamsCurrentName: cySelector('settings.teams.current.name'),
253
+ teamsCurrentSlug: cySelector('settings.teams.current.slug'),
254
+ teamsCurrentDescription: cySelector('settings.teams.current.description'),
255
+ teamsCurrentAvatarContainer: cySelector('settings.teams.current.avatar.container'),
256
+ teamsCurrentAvatarUpload: cySelector('settings.teams.current.avatar.upload'),
257
+ teamsCurrentSubmit: cySelector('settings.teams.current.submitButton'),
258
+ teamsCurrentDelete: cySelector('settings.teams.current.deleteButton'),
259
+ teamsCurrentDeleteDialogContainer: cySelector('settings.teams.current.deleteDialog.container'),
260
+ teamsCurrentDeleteDialogConfirm: cySelector('settings.teams.current.deleteDialog.confirmButton'),
261
+ // Members (ex members.*)
262
+ teamsMembersContainer: cySelector('settings.teams.members.container'),
263
+ teamsMembersList: cySelector('settings.teams.members.list'),
264
+ teamsMembersRowContainer: (id: string) => cySelector('settings.teams.members.row.container', { id }),
265
+ teamsMembersRowGeneric: '[data-cy^="member-row-"]',
266
+ teamsMembersRowAvatar: (id: string) => cySelector('settings.teams.members.row.avatar', { id }),
267
+ teamsMembersRowName: (id: string) => cySelector('settings.teams.members.row.name', { id }),
268
+ teamsMembersRowEmail: (id: string) => cySelector('settings.teams.members.row.email', { id }),
269
+ teamsMembersRowRole: (id: string) => cySelector('settings.teams.members.row.role', { id }),
270
+ teamsMembersRowRemove: (id: string) => cySelector('settings.teams.members.row.removeButton', { id }),
271
+ teamsMembersInviteButton: cySelector('settings.teams.members.inviteButton'),
272
+ teamsMembersInviteDialogContainer: cySelector('settings.teams.members.inviteDialog.container'),
273
+ teamsMembersInviteDialogEmail: cySelector('settings.teams.members.inviteDialog.emailInput'),
274
+ teamsMembersInviteDialogRole: cySelector('settings.teams.members.inviteDialog.roleSelect'),
275
+ teamsMembersInviteDialogSubmit: cySelector('settings.teams.members.inviteDialog.submitButton'),
276
+ teamsMembersPendingContainer: cySelector('settings.teams.members.pendingInvites.container'),
277
+ teamsMembersPendingRow: (id: string) => cySelector('settings.teams.members.pendingInvites.row', { id }),
278
+ teamsMembersPendingCancel: (id: string) => cySelector('settings.teams.members.pendingInvites.cancelButton', { id }),
279
+ // Teams List (ex teams.*)
280
+ teamsListContainer: cySelector('settings.teams.list.container'),
281
+ teamsListTeamCard: (id: string) => cySelector('settings.teams.list.teamCard', { id }),
282
+ teamsListCreateButton: cySelector('settings.teams.list.createButton'),
283
+ teamsListDetails: (id: string) => cySelector('settings.teams.list.details', { id }),
191
284
  }
192
285
  }
193
286
 
194
287
  // ============================================
195
- // NAVIGATION
288
+ // NAVIGATION (9 methods - 1 per section)
196
289
  // ============================================
197
290
 
198
291
  /**
199
- * Navigate to settings home
292
+ * Navigate to settings overview (home)
200
293
  */
201
294
  visitSettings() {
202
295
  cy.visit('/dashboard/settings', { timeout: 60000 })
@@ -212,26 +305,26 @@ export class SettingsPOM extends BasePOM {
212
305
  }
213
306
 
214
307
  /**
215
- * Navigate to team settings
308
+ * Navigate to password settings
216
309
  */
217
- visitTeam() {
218
- cy.visit('/dashboard/settings/team', { timeout: 60000 })
310
+ visitPassword() {
311
+ cy.visit('/dashboard/settings/password', { timeout: 60000 })
219
312
  return this
220
313
  }
221
314
 
222
315
  /**
223
- * Navigate to billing settings
316
+ * Navigate to security settings (2FA, sessions)
224
317
  */
225
- visitBilling() {
226
- cy.visit('/dashboard/settings/billing', { timeout: 60000 })
318
+ visitSecurity() {
319
+ cy.visit('/dashboard/settings/security', { timeout: 60000 })
227
320
  return this
228
321
  }
229
322
 
230
323
  /**
231
- * Navigate to members settings
324
+ * Navigate to notifications settings
232
325
  */
233
- visitMembers() {
234
- cy.visit('/dashboard/settings/members', { timeout: 60000 })
326
+ visitNotifications() {
327
+ cy.visit('/dashboard/settings/notifications', { timeout: 60000 })
235
328
  return this
236
329
  }
237
330
 
@@ -244,10 +337,18 @@ export class SettingsPOM extends BasePOM {
244
337
  }
245
338
 
246
339
  /**
247
- * Navigate to password settings
340
+ * Navigate to billing settings
248
341
  */
249
- visitPassword() {
250
- cy.visit('/dashboard/settings/password', { timeout: 60000 })
342
+ visitBilling() {
343
+ cy.visit('/dashboard/settings/billing', { timeout: 60000 })
344
+ return this
345
+ }
346
+
347
+ /**
348
+ * Navigate to teams settings
349
+ */
350
+ visitTeams() {
351
+ cy.visit('/dashboard/settings/teams', { timeout: 60000 })
251
352
  return this
252
353
  }
253
354
 
@@ -255,22 +356,30 @@ export class SettingsPOM extends BasePOM {
255
356
  * Click on a settings nav item
256
357
  */
257
358
  clickNavItem(section: string) {
258
- cy.get(this.selectors.navItem(section)).click()
359
+ cy.get(this.selectors.sidebarNavItem(section)).click()
259
360
  return this
260
361
  }
261
362
 
262
363
  // ============================================
263
- // ASSERTIONS
364
+ // ASSERTIONS (12 methods)
264
365
  // ============================================
265
366
 
266
367
  /**
267
- * Assert settings page is visible
368
+ * Assert settings page is visible (any settings page)
268
369
  */
269
370
  assertSettingsVisible() {
270
371
  cy.url().should('include', '/settings')
271
372
  return this
272
373
  }
273
374
 
375
+ /**
376
+ * Assert overview page is visible
377
+ */
378
+ assertOverviewVisible() {
379
+ cy.get(this.selectors.overviewContainer).should('be.visible')
380
+ return this
381
+ }
382
+
274
383
  /**
275
384
  * Assert profile settings is visible
276
385
  */
@@ -280,10 +389,34 @@ export class SettingsPOM extends BasePOM {
280
389
  }
281
390
 
282
391
  /**
283
- * Assert team settings is visible
392
+ * Assert password settings is visible
393
+ */
394
+ assertPasswordVisible() {
395
+ cy.get(this.selectors.passwordContainer).should('be.visible')
396
+ return this
397
+ }
398
+
399
+ /**
400
+ * Assert security settings is visible
401
+ */
402
+ assertSecurityVisible() {
403
+ cy.get(this.selectors.securityContainer).should('be.visible')
404
+ return this
405
+ }
406
+
407
+ /**
408
+ * Assert notifications settings is visible
409
+ */
410
+ assertNotificationsVisible() {
411
+ cy.get(this.selectors.notificationsContainer).should('be.visible')
412
+ return this
413
+ }
414
+
415
+ /**
416
+ * Assert API keys settings is visible
284
417
  */
285
- assertTeamVisible() {
286
- cy.get(this.selectors.teamContainer).should('be.visible')
418
+ assertApiKeysVisible() {
419
+ cy.get(this.selectors.apiKeysContainer).should('be.visible')
287
420
  return this
288
421
  }
289
422
 
@@ -295,11 +428,19 @@ export class SettingsPOM extends BasePOM {
295
428
  return this
296
429
  }
297
430
 
431
+ /**
432
+ * Assert teams settings is visible
433
+ */
434
+ assertTeamsVisible() {
435
+ cy.get(this.selectors.teamsContainer).should('be.visible')
436
+ return this
437
+ }
438
+
298
439
  /**
299
440
  * Assert nav item is visible
300
441
  */
301
442
  assertNavItemVisible(section: string) {
302
- cy.get(this.selectors.navItem(section)).should('be.visible')
443
+ cy.get(this.selectors.sidebarNavItem(section)).should('be.visible')
303
444
  return this
304
445
  }
305
446
 
@@ -307,36 +448,74 @@ export class SettingsPOM extends BasePOM {
307
448
  * Assert nav item is NOT visible (restricted)
308
449
  */
309
450
  assertNavItemNotVisible(section: string) {
310
- cy.get(this.selectors.navItem(section)).should('not.exist')
451
+ cy.get(this.selectors.sidebarNavItem(section)).should('not.exist')
311
452
  return this
312
453
  }
313
454
 
455
+ // ============================================
456
+ // WAITS (9 methods - 1 per section)
457
+ // ============================================
458
+
314
459
  /**
315
- * Assert members container is visible
460
+ * Wait for settings page to load (sidebar visible)
316
461
  */
317
- assertMembersVisible() {
318
- cy.get(this.selectors.membersContainer).should('be.visible')
462
+ waitForSettings() {
463
+ cy.url().should('include', '/settings')
464
+ cy.get(this.selectors.sidebarContainer, { timeout: 15000 }).should('be.visible')
319
465
  return this
320
466
  }
321
467
 
322
468
  /**
323
- * Assert API keys container is visible
469
+ * Wait for overview page to load
324
470
  */
325
- assertApiKeysVisible() {
326
- cy.get(this.selectors.apiKeysContainer).should('be.visible')
471
+ waitForOverview() {
472
+ cy.url().should('match', /\/settings\/?$/)
473
+ cy.get(this.selectors.overviewContainer, { timeout: 15000 }).should('be.visible')
327
474
  return this
328
475
  }
329
476
 
330
- // ============================================
331
- // WAITS
332
- // ============================================
477
+ /**
478
+ * Wait for profile page to load
479
+ */
480
+ waitForProfile() {
481
+ cy.url().should('include', '/settings/profile')
482
+ cy.get(this.selectors.profileContainer, { timeout: 15000 }).should('be.visible')
483
+ return this
484
+ }
333
485
 
334
486
  /**
335
- * Wait for settings page to load
487
+ * Wait for password page to load
336
488
  */
337
- waitForSettings() {
338
- cy.url().should('include', '/settings')
339
- cy.get(this.selectors.navContainer, { timeout: 15000 }).should('be.visible')
489
+ waitForPassword() {
490
+ cy.url().should('include', '/settings/password')
491
+ cy.get(this.selectors.passwordContainer, { timeout: 15000 }).should('be.visible')
492
+ return this
493
+ }
494
+
495
+ /**
496
+ * Wait for security page to load
497
+ */
498
+ waitForSecurity() {
499
+ cy.url().should('include', '/settings/security')
500
+ cy.get(this.selectors.securityContainer, { timeout: 15000 }).should('be.visible')
501
+ return this
502
+ }
503
+
504
+ /**
505
+ * Wait for notifications page to load
506
+ */
507
+ waitForNotifications() {
508
+ cy.url().should('include', '/settings/notifications')
509
+ cy.get(this.selectors.notificationsContainer, { timeout: 15000 }).should('be.visible')
510
+ return this
511
+ }
512
+
513
+ /**
514
+ * Wait for API keys page to load
515
+ */
516
+ waitForApiKeys() {
517
+ cy.url().should('include', '/settings/api-keys')
518
+ cy.get(this.selectors.apiKeysContainer, { timeout: 15000 }).should('be.visible')
340
519
  return this
341
520
  }
342
521
 
@@ -350,11 +529,177 @@ export class SettingsPOM extends BasePOM {
350
529
  }
351
530
 
352
531
  /**
353
- * Wait for team page to load
532
+ * Wait for teams page to load
533
+ */
534
+ waitForTeams() {
535
+ cy.url().should('include', '/settings/teams')
536
+ cy.get(this.selectors.teamsContainer, { timeout: 15000 }).should('be.visible')
537
+ return this
538
+ }
539
+
540
+ // ============================================
541
+ // TEAM INLINE EDIT METHODS
542
+ // ============================================
543
+
544
+ /**
545
+ * Click edit icon for team name
546
+ */
547
+ editTeamName() {
548
+ cy.get(this.selectors.teamEditNameEditIcon).click()
549
+ return this
550
+ }
551
+
552
+ /**
553
+ * Type into team name input field
554
+ */
555
+ typeTeamName(name: string) {
556
+ cy.get(this.selectors.teamEditNameInput).clear().type(name)
557
+ return this
558
+ }
559
+
560
+ /**
561
+ * Click save icon for team name
562
+ */
563
+ saveTeamName() {
564
+ cy.get(this.selectors.teamEditNameSaveIcon).click()
565
+ return this
566
+ }
567
+
568
+ /**
569
+ * Click cancel icon for team name
570
+ */
571
+ cancelTeamName() {
572
+ cy.get(this.selectors.teamEditNameCancelIcon).click()
573
+ return this
574
+ }
575
+
576
+ /**
577
+ * Get the current team name text value
578
+ */
579
+ getTeamNameText() {
580
+ return cy.get(this.selectors.teamEditNameValue).invoke('text')
581
+ }
582
+
583
+ /**
584
+ * Click edit icon for team description
585
+ */
586
+ editTeamDescription() {
587
+ cy.get(this.selectors.teamEditDescriptionEditIcon).click()
588
+ return this
589
+ }
590
+
591
+ /**
592
+ * Type into team description textarea field
593
+ */
594
+ typeTeamDescription(description: string) {
595
+ cy.get(this.selectors.teamEditDescriptionTextarea).clear().type(description)
596
+ return this
597
+ }
598
+
599
+ /**
600
+ * Click save icon for team description
601
+ */
602
+ saveTeamDescription() {
603
+ cy.get(this.selectors.teamEditDescriptionSaveIcon).click()
604
+ return this
605
+ }
606
+
607
+ /**
608
+ * Click cancel icon for team description
609
+ */
610
+ cancelTeamDescription() {
611
+ cy.get(this.selectors.teamEditDescriptionCancelIcon).click()
612
+ return this
613
+ }
614
+
615
+ /**
616
+ * Get the current team description text value
617
+ */
618
+ getTeamDescriptionText() {
619
+ return cy.get(this.selectors.teamEditDescriptionValue).invoke('text')
620
+ }
621
+
622
+ /**
623
+ * Assert team name edit button is visible (owner only)
624
+ */
625
+ assertTeamNameEditable() {
626
+ cy.get(this.selectors.teamEditNameEditIcon).should('be.visible')
627
+ return this
628
+ }
629
+
630
+ /**
631
+ * Assert team name edit button is NOT visible (non-owner)
632
+ */
633
+ assertTeamNameNotEditable() {
634
+ cy.get(this.selectors.teamEditNameEditIcon).should('not.exist')
635
+ return this
636
+ }
637
+
638
+ /**
639
+ * Assert team description edit button is visible (owner only)
640
+ */
641
+ assertTeamDescriptionEditable() {
642
+ cy.get(this.selectors.teamEditDescriptionEditIcon).should('be.visible')
643
+ return this
644
+ }
645
+
646
+ /**
647
+ * Assert team description edit button is NOT visible (non-owner)
648
+ */
649
+ assertTeamDescriptionNotEditable() {
650
+ cy.get(this.selectors.teamEditDescriptionEditIcon).should('not.exist')
651
+ return this
652
+ }
653
+
654
+ /**
655
+ * Edit and save team name (complete flow)
656
+ */
657
+ editAndSaveTeamName(newName: string) {
658
+ this.editTeamName()
659
+ this.typeTeamName(newName)
660
+ this.saveTeamName()
661
+ return this
662
+ }
663
+
664
+ /**
665
+ * Edit and save team description (complete flow)
666
+ */
667
+ editAndSaveTeamDescription(newDescription: string) {
668
+ this.editTeamDescription()
669
+ this.typeTeamDescription(newDescription)
670
+ this.saveTeamDescription()
671
+ return this
672
+ }
673
+
674
+ /**
675
+ * Assert team name error message is visible
676
+ */
677
+ assertTeamNameError() {
678
+ cy.get(this.selectors.teamEditNameError).should('be.visible')
679
+ return this
680
+ }
681
+
682
+ /**
683
+ * Assert team name success message is visible
684
+ */
685
+ assertTeamNameSuccess() {
686
+ cy.get(this.selectors.teamEditNameSuccess).should('be.visible')
687
+ return this
688
+ }
689
+
690
+ /**
691
+ * Assert team description error message is visible
692
+ */
693
+ assertTeamDescriptionError() {
694
+ cy.get(this.selectors.teamEditDescriptionError).should('be.visible')
695
+ return this
696
+ }
697
+
698
+ /**
699
+ * Assert team description success message is visible
354
700
  */
355
- waitForTeam() {
356
- cy.url().should('include', '/settings/team')
357
- cy.get(this.selectors.teamContainer, { timeout: 15000 }).should('be.visible')
701
+ assertTeamDescriptionSuccess() {
702
+ cy.get(this.selectors.teamEditDescriptionSuccess).should('be.visible')
358
703
  return this
359
704
  }
360
705
  }