@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,18 @@
1
+ /**
2
+ * Starter Theme - Feature POM exports
3
+ *
4
+ * Specialized POMs for core features:
5
+ * - DashboardPOM: Dashboard navigation and shell
6
+ * - SettingsPOM: Settings area (profile, team, billing)
7
+ * - SuperadminPOM: Superadmin area
8
+ * - BillingPOM: Billing settings
9
+ * - DevtoolsPOM: Developer tools area
10
+ * - ScheduledActionsPOM: Scheduled actions devtools
11
+ */
12
+
13
+ export { DashboardPOM } from './DashboardPOM'
14
+ export { SettingsPOM } from './SettingsPOM'
15
+ export { SuperadminPOM } from './SuperadminPOM'
16
+ export { BillingPOM } from './BillingPOM'
17
+ export { DevtoolsPOM } from './DevtoolsPOM'
18
+ export { ScheduledActionsPOM } from './ScheduledActionsPOM'
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Starter Theme - Cypress POM Exports
3
+ *
4
+ * Centralized exports for all Starter theme Page Object Models and helpers.
5
+ *
6
+ * Architecture:
7
+ * - core/: Base classes (BasePOM, DashboardEntityPOM, BlockEditorBasePOM, AuthPOM)
8
+ * - components/: Generic UI components (EntityForm, EntityList, TeamSwitcherPOM)
9
+ * - entities/: Entity POMs extending DashboardEntityPOM (TasksPOM)
10
+ * - features/: Feature POMs (DashboardPOM, SettingsPOM, SuperadminPOM, etc.)
11
+ * - helpers/: Utility classes (ApiInterceptor)
12
+ *
13
+ * Usage:
14
+ * import { TasksPOM, AuthPOM, DashboardPOM } from '../src'
15
+ * const tasks = TasksPOM.create()
16
+ * tasks.visitList().waitForList().clickAdd()
17
+ *
18
+ * // Session helpers
19
+ * import { loginAsOwner, loginAsMember } from '../src'
20
+ */
21
+
22
+ // ============================================
23
+ // CORE - Base Classes
24
+ // ============================================
25
+ export {
26
+ BasePOM,
27
+ DashboardEntityPOM,
28
+ BlockEditorBasePOM,
29
+ AuthPOM,
30
+ type EntityConfig,
31
+ type SignupData
32
+ } from './core'
33
+
34
+ // ============================================
35
+ // COMPONENTS - Generic UI Components
36
+ // ============================================
37
+ export {
38
+ EntityList,
39
+ EntityForm,
40
+ TeamSwitcherPOM,
41
+ DevKeyringPOM,
42
+ type EntityListConfig,
43
+ type EntityFormConfig
44
+ } from './components'
45
+
46
+ // ============================================
47
+ // ENTITIES - Entity POMs (extend DashboardEntityPOM)
48
+ // ============================================
49
+ export {
50
+ TasksPOM,
51
+ type TaskFormData
52
+ } from './entities'
53
+
54
+ // ============================================
55
+ // FEATURES - Feature POMs
56
+ // ============================================
57
+ export {
58
+ DashboardPOM,
59
+ SettingsPOM,
60
+ SuperadminPOM,
61
+ BillingPOM,
62
+ DevtoolsPOM,
63
+ ScheduledActionsPOM
64
+ } from './features'
65
+
66
+ // ============================================
67
+ // HELPERS
68
+ // ============================================
69
+ export { ApiInterceptor, type ApiInterceptorConfig } from './helpers/ApiInterceptor'
70
+
71
+ // ============================================
72
+ // SESSION HELPERS
73
+ // ============================================
74
+ export {
75
+ DEFAULT_THEME_USERS,
76
+ loginAsDefaultOwner,
77
+ loginAsDefaultAdmin,
78
+ loginAsDefaultMember,
79
+ loginAsDefaultEditor,
80
+ loginAsDefaultViewer,
81
+ loginAsOwner,
82
+ loginAsMember,
83
+ loginAsAdmin,
84
+ loginAsEditor,
85
+ loginAsViewer,
86
+ loginAsDefaultDeveloper,
87
+ loginAsDefaultSuperadmin,
88
+ } from './session-helpers'
@@ -1,46 +1,68 @@
1
1
  /**
2
- * Starter Theme Session Helpers
2
+ * Default Theme Session Helpers
3
3
  *
4
- * Direct login functions for theme tests using theme-specific users.
4
+ * Direct login functions for Default theme tests using theme-specific users.
5
5
  * These helpers don't depend on ACTIVE_THEME environment variable.
6
6
  *
7
- * IMPORTANT: After login, this helper sets activeTeamId in localStorage.
8
- * This is required because all entity API calls include x-team-id header
9
- * which is read from localStorage.activeTeamId.
7
+ * EXPERIMENTAL: Uses API-based login instead of UI login.
8
+ * Hypothesis: API login is faster and more stable than DevKeyring UI login,
9
+ * especially with slow dev servers (Turbopack on-demand compilation).
10
10
  *
11
- * CONFIGURATION: Credentials can be customized via Cypress env variables:
12
- * - CYPRESS_DEVELOPER_EMAIL: Override developer user email
13
- * - CYPRESS_DEVELOPER_PASSWORD: Override developer user password
14
- * - CYPRESS_OWNER_EMAIL, CYPRESS_ADMIN_EMAIL, etc.: Override specific roles
11
+ * Test file: cypress/e2e/_experimental/api-login-test.cy.ts
12
+ * Fallback: If API fails, falls back to UI login with DevKeyring.
15
13
  *
16
- * Set these in cypress.config.ts env section or via CLI:
17
- * pnpm cy:open --env DEVELOPER_EMAIL=myuser@example.com,DEVELOPER_PASSWORD=MyPass123
14
+ * IMPORTANT: After login, this helper sets activeTeamId in localStorage.
15
+ * This is required because all entity API calls include x-team-id header
16
+ * which is read from localStorage.activeTeamId (see core/lib/api/entities.ts).
17
+ * Without this, API calls return 400 "Team context required".
18
18
  */
19
19
 
20
+ import { DevKeyringPOM as DevKeyring } from './components/DevKeyringPOM'
21
+
20
22
  /**
21
23
  * Environment-based Test Credentials
22
- * Fallback values use developer@nextspark.dev (pre-installed from core)
24
+ *
25
+ * These can be overridden via Cypress env variables (cypress.config.ts or CLI):
26
+ * - CYPRESS_DEVELOPER_EMAIL / CYPRESS_DEVELOPER_PASSWORD - Developer user
27
+ * - CYPRESS_SUPERADMIN_EMAIL / CYPRESS_SUPERADMIN_PASSWORD - Superadmin user
28
+ * - CYPRESS_OWNER_EMAIL, CYPRESS_ADMIN_EMAIL, etc. - Demo theme users
29
+ *
30
+ * Fallback values are the default users from core and theme sample data.
23
31
  */
32
+
33
+ // Core system user credentials (configurable via env)
24
34
  const DEVELOPER_EMAIL = Cypress.env('DEVELOPER_EMAIL') || 'developer@nextspark.dev'
25
35
  const DEVELOPER_PASSWORD = Cypress.env('DEVELOPER_PASSWORD') || 'Pandora1234'
36
+ const SUPERADMIN_PASSWORD = Cypress.env('SUPERADMIN_PASSWORD') || 'Pandora1234'
37
+
38
+ // Demo user password (configurable via env)
39
+ const TEST_PASSWORD = Cypress.env('TEST_PASSWORD') || 'Test1234'
26
40
 
27
41
  /**
28
- * Theme Test Users
29
- * Using developer@nextspark.dev which comes pre-installed from core
30
- * and has full platform access (superadmin + developer role)
42
+ * Default Theme Test Users
43
+ * Teams: Everpoint Labs, Ironvale Global, Riverstone Ventures
31
44
  *
32
- * All roles use the same user for simplicity in starter theme.
33
- * Customize via CYPRESS_DEVELOPER_EMAIL env variable.
45
+ * Note: These are fallback demo users. For selector tests, use CORE_USER (developer).
34
46
  */
35
- export const THEME_USERS = {
36
- OWNER: Cypress.env('OWNER_EMAIL') || DEVELOPER_EMAIL,
37
- ADMIN: Cypress.env('ADMIN_EMAIL') || DEVELOPER_EMAIL,
38
- MEMBER: Cypress.env('MEMBER_EMAIL') || DEVELOPER_EMAIL,
39
- VIEWER: Cypress.env('VIEWER_EMAIL') || DEVELOPER_EMAIL,
47
+ export const DEFAULT_THEME_USERS = {
48
+ OWNER: Cypress.env('OWNER_EMAIL') || 'carlos.mendoza@nextspark.dev',
49
+ ADMIN: Cypress.env('ADMIN_EMAIL') || 'james.wilson@nextspark.dev',
50
+ MEMBER: Cypress.env('MEMBER_EMAIL') || 'emily.johnson@nextspark.dev',
51
+ EDITOR: Cypress.env('EDITOR_EMAIL') || 'diego.ramirez@nextspark.dev',
52
+ VIEWER: Cypress.env('VIEWER_EMAIL') || 'sarah.davis@nextspark.dev',
40
53
  } as const
41
54
 
42
- // Common password for all test users (configurable via env)
43
- const TEST_PASSWORD = DEVELOPER_PASSWORD
55
+ /**
56
+ * Core System Users (from core/migrations/090_sample_data.sql)
57
+ * These users have special global roles, not team-based roles
58
+ *
59
+ * IMPORTANT: DEVELOPER is the recommended user for most tests.
60
+ * Configurable via CYPRESS_DEVELOPER_EMAIL env variable.
61
+ */
62
+ export const CORE_USERS = {
63
+ SUPERADMIN: Cypress.env('SUPERADMIN_EMAIL') || 'superadmin@nextspark.dev',
64
+ DEVELOPER: DEVELOPER_EMAIL,
65
+ } as const
44
66
 
45
67
  // Extended timeout for dev server compilation (60s for slow cold starts)
46
68
  const API_TIMEOUT = 60000
@@ -54,8 +76,10 @@ const API_TIMEOUT = 60000
54
76
  * 3. Calls /api/v1/teams/switch to set server-side team cookie
55
77
  *
56
78
  * Without this, all entity API calls return 400 "Team context required"
79
+ *
80
+ * @param preferredRole - Optional role to filter teams by (e.g., 'member' to select team where user is member)
57
81
  */
58
- function setupTeamContext() {
82
+ function setupTeamContext(preferredRole?: string) {
59
83
  cy.request({
60
84
  method: 'GET',
61
85
  url: '/api/v1/teams',
@@ -63,10 +87,23 @@ function setupTeamContext() {
63
87
  failOnStatusCode: false
64
88
  }).then((teamsResponse) => {
65
89
  if (teamsResponse.status === 200 && teamsResponse.body?.data?.length > 0) {
66
- const firstTeam = teamsResponse.body.data[0]
67
- const teamId = firstTeam.id
90
+ const teams = teamsResponse.body.data
68
91
 
69
- cy.log(`Setting active team: ${firstTeam.name} (${teamId})`)
92
+ // If preferredRole specified, find team where user has that role
93
+ let selectedTeam = teams[0]
94
+ if (preferredRole) {
95
+ const teamWithRole = teams.find((t: { role: string }) => t.role === preferredRole)
96
+ if (teamWithRole) {
97
+ selectedTeam = teamWithRole
98
+ cy.log(`✅ Found team with role "${preferredRole}": ${selectedTeam.name}`)
99
+ } else {
100
+ cy.log(`⚠️ No team found with role "${preferredRole}", using first team`)
101
+ }
102
+ }
103
+
104
+ const teamId = selectedTeam.id
105
+
106
+ cy.log(`✅ Setting active team: ${selectedTeam.name} (${teamId}) - role: ${selectedTeam.role}`)
70
107
 
71
108
  // Set in localStorage (used by frontend buildHeaders() to add x-team-id)
72
109
  cy.window().then((win) => {
@@ -82,7 +119,7 @@ function setupTeamContext() {
82
119
  failOnStatusCode: false
83
120
  })
84
121
  } else {
85
- cy.log(`Warning: No teams found for user, API calls requiring team context will fail`)
122
+ cy.log(`⚠️ No teams found for user, API calls requiring team context will fail`)
86
123
  }
87
124
  })
88
125
  }
@@ -90,47 +127,64 @@ function setupTeamContext() {
90
127
  /**
91
128
  * Login via API (faster and more stable than UI)
92
129
  * Returns true if API login succeeded, false if fallback to UI was needed.
130
+ *
131
+ * @param email - User email to login
132
+ * @param password - Optional password (defaults to TEST_PASSWORD for demo users)
93
133
  */
94
- function apiLogin(email: string): Cypress.Chainable<boolean> {
134
+ function apiLogin(email: string, password: string = TEST_PASSWORD): Cypress.Chainable<boolean> {
95
135
  return cy.request({
96
136
  method: 'POST',
97
137
  url: '/api/auth/sign-in/email',
98
- body: { email, password: TEST_PASSWORD },
138
+ body: { email, password },
99
139
  timeout: API_TIMEOUT,
100
140
  failOnStatusCode: false
101
141
  }).then((response) => {
102
142
  if (response.status === 200) {
103
143
  return true
104
144
  } else {
105
- cy.log(`Warning: API login failed with status ${response.status}, falling back to UI login`)
145
+ cy.log(`⚠️ API login failed with status ${response.status}, falling back to UI login`)
106
146
  // Fallback to UI login if API fails
107
147
  cy.visit('/login', { timeout: 60000 })
148
+ const devKeyring = new DevKeyring()
149
+ devKeyring.validateVisible()
150
+ devKeyring.quickLoginByEmail(email)
108
151
  return false
109
152
  }
110
153
  })
111
154
  }
112
155
 
113
156
  /**
114
- * Login as Owner
157
+ * Login as Default Theme Owner
115
158
  * Session is cached and reused across tests
159
+ *
160
+ * Flow:
161
+ * 1. API login (or UI fallback)
162
+ * 2. Visit dashboard to load page context
163
+ * 3. Setup team context (sets localStorage.activeTeamId)
116
164
  */
117
- export function loginAsOwner() {
118
- cy.session('owner-session', () => {
119
- apiLogin(THEME_USERS.OWNER).then((apiLoginSucceeded) => {
165
+ export function loginAsDefaultOwner() {
166
+ cy.session('default-owner-session', () => {
167
+ apiLogin(DEFAULT_THEME_USERS.OWNER).then((apiLoginSucceeded) => {
168
+ // If API login succeeded, we need to visit a page before setting localStorage
120
169
  if (apiLoginSucceeded) {
121
170
  cy.visit('/dashboard', { timeout: 60000 })
122
171
  }
172
+ // URL assertion to ensure page loaded
123
173
  cy.url().should('include', '/dashboard')
174
+ // Setup team context (requires page to be loaded for localStorage)
124
175
  setupTeamContext()
125
176
  })
126
177
  }, {
127
178
  validate: () => {
179
+ // Validate auth session exists
128
180
  cy.request({
129
181
  url: '/api/auth/get-session',
130
182
  timeout: API_TIMEOUT,
131
183
  failOnStatusCode: false
132
184
  }).its('status').should('eq', 200)
133
185
 
186
+ // Validate team context exists in localStorage
187
+ // This ensures API calls will have x-team-id header
134
188
  cy.window().then((win) => {
135
189
  const teamId = win.localStorage.getItem('activeTeamId')
136
190
  expect(teamId, 'activeTeamId should exist in localStorage').to.not.be.null
@@ -141,12 +195,12 @@ export function loginAsOwner() {
141
195
  }
142
196
 
143
197
  /**
144
- * Login as Admin
198
+ * Login as Default Theme Admin
145
199
  * Session is cached and reused across tests
146
200
  */
147
- export function loginAsAdmin() {
148
- cy.session('admin-session', () => {
149
- apiLogin(THEME_USERS.ADMIN).then((apiLoginSucceeded) => {
201
+ export function loginAsDefaultAdmin() {
202
+ cy.session('default-admin-session', () => {
203
+ apiLogin(DEFAULT_THEME_USERS.ADMIN).then((apiLoginSucceeded) => {
150
204
  if (apiLoginSucceeded) {
151
205
  cy.visit('/dashboard', { timeout: 60000 })
152
206
  }
@@ -165,17 +219,21 @@ export function loginAsAdmin() {
165
219
  }
166
220
 
167
221
  /**
168
- * Login as Member
222
+ * Login as Default Theme Member
169
223
  * Session is cached and reused across tests
224
+ *
225
+ * Note: Emily Johnson is member of Everpoint but admin of Riverstone.
226
+ * We explicitly select the team where she has 'member' role.
170
227
  */
171
- export function loginAsMember() {
172
- cy.session('member-session', () => {
173
- apiLogin(THEME_USERS.MEMBER).then((apiLoginSucceeded) => {
228
+ export function loginAsDefaultMember() {
229
+ cy.session('default-member-session', () => {
230
+ apiLogin(DEFAULT_THEME_USERS.MEMBER).then((apiLoginSucceeded) => {
174
231
  if (apiLoginSucceeded) {
175
232
  cy.visit('/dashboard', { timeout: 60000 })
176
233
  }
177
234
  cy.url().should('include', '/dashboard')
178
- setupTeamContext()
235
+ // Explicitly select team where user is member (not admin)
236
+ setupTeamContext('member')
179
237
  })
180
238
  }, {
181
239
  validate: () => {
@@ -189,12 +247,12 @@ export function loginAsMember() {
189
247
  }
190
248
 
191
249
  /**
192
- * Login as Viewer
250
+ * Login as Default Theme Viewer
193
251
  * Session is cached and reused across tests
194
252
  */
195
- export function loginAsViewer() {
196
- cy.session('viewer-session', () => {
197
- apiLogin(THEME_USERS.VIEWER).then((apiLoginSucceeded) => {
253
+ export function loginAsDefaultViewer() {
254
+ cy.session('default-viewer-session', () => {
255
+ apiLogin(DEFAULT_THEME_USERS.VIEWER).then((apiLoginSucceeded) => {
198
256
  if (apiLoginSucceeded) {
199
257
  cy.visit('/dashboard', { timeout: 60000 })
200
258
  }
@@ -213,62 +271,248 @@ export function loginAsViewer() {
213
271
  }
214
272
 
215
273
  /**
216
- * Login with custom session management
217
- * Useful for specific email/password combinations
274
+ * Login as Default Theme Editor
275
+ * Session is cached and reused across tests
276
+ *
277
+ * Editor is a custom role with limited permissions:
278
+ * - Can view/list customers but cannot create/update/delete
279
+ * - Cannot access Admin or Dev Zone
218
280
  */
219
- export function loginWithSession(email: string, password: string) {
220
- const sessionId = `session-${email.replace(/[@.]/g, '-')}`
221
-
222
- cy.session(sessionId, () => {
223
- cy.request({
224
- method: 'POST',
225
- url: '/api/auth/sign-in/email',
226
- body: { email, password },
227
- timeout: API_TIMEOUT,
228
- failOnStatusCode: false
229
- }).then((response) => {
230
- if (response.status === 200) {
281
+ export function loginAsDefaultEditor() {
282
+ cy.session('default-editor-session', () => {
283
+ apiLogin(DEFAULT_THEME_USERS.EDITOR).then((apiLoginSucceeded) => {
284
+ if (apiLoginSucceeded) {
231
285
  cy.visit('/dashboard', { timeout: 60000 })
232
- cy.url().should('include', '/dashboard')
233
- setupTeamContext()
234
- } else {
235
- throw new Error(`Login failed for ${email}: ${response.body?.error || 'Unknown error'}`)
236
286
  }
287
+ cy.url().should('include', '/dashboard')
288
+ setupTeamContext()
237
289
  })
290
+ }, {
291
+ validate: () => {
292
+ cy.request({
293
+ url: '/api/auth/get-session',
294
+ timeout: API_TIMEOUT,
295
+ failOnStatusCode: false
296
+ }).its('status').should('eq', 200)
297
+ }
238
298
  })
239
299
  }
240
300
 
241
301
  /**
242
- * Login as a specific role
302
+ * Login as Superadmin (core system user)
303
+ * Session is cached and reused across tests
304
+ *
305
+ * Superadmin has global access:
306
+ * - Full Admin access
307
+ * - Not team-based (no setupTeamContext needed)
243
308
  */
244
- export function loginAsRole(role: 'owner' | 'admin' | 'member' | 'viewer') {
245
- const loginFunctions = {
246
- owner: loginAsOwner,
247
- admin: loginAsAdmin,
248
- member: loginAsMember,
249
- viewer: loginAsViewer,
250
- }
251
-
252
- const loginFn = loginFunctions[role]
253
- if (loginFn) {
254
- loginFn()
255
- } else {
256
- throw new Error(`Unknown role: ${role}`)
257
- }
309
+ export function loginAsDefaultSuperadmin() {
310
+ cy.session('default-superadmin-session', () => {
311
+ apiLogin(CORE_USERS.SUPERADMIN, SUPERADMIN_PASSWORD).then((apiLoginSucceeded) => {
312
+ if (apiLoginSucceeded) {
313
+ cy.visit('/superadmin', { timeout: 60000 })
314
+ }
315
+ // Superadmin should land on superadmin panel or dashboard
316
+ cy.url().should('satisfy', (url: string) => {
317
+ return url.includes('/superadmin') || url.includes('/dashboard')
318
+ })
319
+ })
320
+ }, {
321
+ validate: () => {
322
+ cy.request({
323
+ url: '/api/auth/get-session',
324
+ timeout: API_TIMEOUT,
325
+ failOnStatusCode: false
326
+ }).its('status').should('eq', 200)
327
+ }
328
+ })
258
329
  }
259
330
 
260
331
  /**
261
- * Clear all session data
332
+ * Login as Developer (core system user)
333
+ * Session is cached and reused across tests
334
+ *
335
+ * Developer has:
336
+ * - Dev Zone access
337
+ * - Not team-based (no setupTeamContext needed)
262
338
  */
263
- export function clearSession() {
264
- cy.clearCookies()
265
- cy.clearLocalStorage()
266
- Cypress.session.clearAllSavedSessions()
339
+ export function loginAsDefaultDeveloper() {
340
+ cy.session('default-developer-session', () => {
341
+ apiLogin(CORE_USERS.DEVELOPER, DEVELOPER_PASSWORD).then((apiLoginSucceeded) => {
342
+ if (apiLoginSucceeded) {
343
+ cy.visit('/devtools', { timeout: 60000 })
344
+ }
345
+ // Developer should land on devtools or dashboard
346
+ cy.url().should('satisfy', (url: string) => {
347
+ return url.includes('/devtools') || url.includes('/dashboard')
348
+ })
349
+ })
350
+ }, {
351
+ validate: () => {
352
+ cy.request({
353
+ url: '/api/auth/get-session',
354
+ timeout: API_TIMEOUT,
355
+ failOnStatusCode: false
356
+ }).its('status').should('eq', 200)
357
+ }
358
+ })
267
359
  }
268
360
 
361
+ // Aliases for convenience
362
+ export const loginAsOwner = loginAsDefaultOwner
363
+ export const loginAsMember = loginAsDefaultMember
364
+ export const loginAsAdmin = loginAsDefaultAdmin
365
+ export const loginAsEditor = loginAsDefaultEditor
366
+ export const loginAsViewer = loginAsDefaultViewer
367
+ export const loginAsSuperadmin = loginAsDefaultSuperadmin
368
+ export const loginAsDeveloper = loginAsDefaultDeveloper
369
+
269
370
  /**
270
371
  * Returns theme users for backwards compatibility with external helper pattern.
372
+ * Used by tests that import { getThemeUsers } from session-helpers.
271
373
  */
272
374
  export function getThemeUsers() {
273
- return THEME_USERS
375
+ return DEFAULT_THEME_USERS
376
+ }
377
+
378
+ // ============================================================
379
+ // BILLING TEST USERS
380
+ // ============================================================
381
+
382
+ /**
383
+ * Billing Test Users - Teams with different subscription plans
384
+ *
385
+ * These users/teams are used to test billing features from different plan perspectives:
386
+ * - Free Plan: Carlos's personal team (team-personal-carlos-001)
387
+ * - Pro Plan: Everpoint Labs (team-everpoint-001)
388
+ * - Enterprise Plan: Ironvale Global (team-ironvale-002)
389
+ */
390
+ export const BILLING_TEAMS = {
391
+ FREE: {
392
+ teamId: 'team-personal-carlos-001',
393
+ name: 'Carlos Personal',
394
+ planSlug: 'free',
395
+ owner: 'carlos.mendoza@nextspark.dev'
396
+ },
397
+ PRO: {
398
+ teamId: 'team-everpoint-001',
399
+ name: 'Everpoint Labs',
400
+ planSlug: 'pro',
401
+ owner: 'carlos.mendoza@nextspark.dev'
402
+ },
403
+ ENTERPRISE: {
404
+ teamId: 'team-ironvale-002',
405
+ name: 'Ironvale Global',
406
+ planSlug: 'enterprise',
407
+ owner: 'ana.garcia@nextspark.dev'
408
+ }
409
+ } as const
410
+
411
+ /**
412
+ * Switch to a specific team after login
413
+ * @param teamId - Team ID to switch to
414
+ */
415
+ function switchToTeam(teamId: string) {
416
+ cy.window().then((win) => {
417
+ win.localStorage.setItem('activeTeamId', teamId)
418
+ })
419
+
420
+ cy.request({
421
+ method: 'POST',
422
+ url: '/api/v1/teams/switch',
423
+ body: { teamId },
424
+ timeout: API_TIMEOUT,
425
+ failOnStatusCode: false
426
+ })
427
+ }
428
+
429
+ /**
430
+ * Login as Carlos and switch to Free plan team
431
+ * Used for testing Free plan restrictions
432
+ */
433
+ export function loginAsFreePlanUser() {
434
+ cy.session('billing-free-plan-session', () => {
435
+ apiLogin(BILLING_TEAMS.FREE.owner).then((apiLoginSucceeded) => {
436
+ if (apiLoginSucceeded) {
437
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
438
+ }
439
+ cy.url().should('include', '/dashboard')
440
+ // Switch to the Free team
441
+ switchToTeam(BILLING_TEAMS.FREE.teamId)
442
+ })
443
+ }, {
444
+ validate: () => {
445
+ cy.request({
446
+ url: '/api/auth/get-session',
447
+ timeout: API_TIMEOUT,
448
+ failOnStatusCode: false
449
+ }).its('status').should('eq', 200)
450
+
451
+ cy.window().then((win) => {
452
+ const teamId = win.localStorage.getItem('activeTeamId')
453
+ expect(teamId).to.eq(BILLING_TEAMS.FREE.teamId)
454
+ })
455
+ }
456
+ })
457
+ }
458
+
459
+ /**
460
+ * Login as Carlos and switch to Pro plan team (Everpoint)
461
+ * Used for testing Pro plan features
462
+ */
463
+ export function loginAsProPlanUser() {
464
+ cy.session('billing-pro-plan-session', () => {
465
+ apiLogin(BILLING_TEAMS.PRO.owner).then((apiLoginSucceeded) => {
466
+ if (apiLoginSucceeded) {
467
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
468
+ }
469
+ cy.url().should('include', '/dashboard')
470
+ // Switch to the Pro team
471
+ switchToTeam(BILLING_TEAMS.PRO.teamId)
472
+ })
473
+ }, {
474
+ validate: () => {
475
+ cy.request({
476
+ url: '/api/auth/get-session',
477
+ timeout: API_TIMEOUT,
478
+ failOnStatusCode: false
479
+ }).its('status').should('eq', 200)
480
+
481
+ cy.window().then((win) => {
482
+ const teamId = win.localStorage.getItem('activeTeamId')
483
+ expect(teamId).to.eq(BILLING_TEAMS.PRO.teamId)
484
+ })
485
+ }
486
+ })
487
+ }
488
+
489
+ /**
490
+ * Login as Ana and switch to Enterprise plan team (Ironvale)
491
+ * Used for testing Enterprise plan features
492
+ */
493
+ export function loginAsEnterprisePlanUser() {
494
+ cy.session('billing-enterprise-plan-session', () => {
495
+ // Ana is owner of Ironvale
496
+ apiLogin(BILLING_TEAMS.ENTERPRISE.owner).then((apiLoginSucceeded) => {
497
+ if (apiLoginSucceeded) {
498
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
499
+ }
500
+ cy.url().should('include', '/dashboard')
501
+ // Switch to the Enterprise team
502
+ switchToTeam(BILLING_TEAMS.ENTERPRISE.teamId)
503
+ })
504
+ }, {
505
+ validate: () => {
506
+ cy.request({
507
+ url: '/api/auth/get-session',
508
+ timeout: API_TIMEOUT,
509
+ failOnStatusCode: false
510
+ }).its('status').should('eq', 200)
511
+
512
+ cy.window().then((win) => {
513
+ const teamId = win.localStorage.getItem('activeTeamId')
514
+ expect(teamId).to.eq(BILLING_TEAMS.ENTERPRISE.teamId)
515
+ })
516
+ }
517
+ })
274
518
  }
@@ -10,8 +10,11 @@
10
10
  import { defineConfig } from 'cypress'
11
11
  import path from 'path'
12
12
  import fs from 'fs'
13
+ import { fileURLToPath } from 'url'
13
14
 
14
- // __dirname works natively with CommonJS module resolution (tsconfig.cypress.json)
15
+ // ESM-compatible __dirname
16
+ const __filename = fileURLToPath(import.meta.url)
17
+ const __dirname = path.dirname(__filename)
15
18
 
16
19
  // Paths relative to this config file
17
20
  const themeRoot = path.resolve(__dirname, '..')