@nextsparkjs/theme-crm 0.1.0-beta.18 → 0.1.0-beta.20
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.
- package/package.json +2 -2
- package/tests/cypress/e2e/api/activities/activities-crud.cy.ts +686 -0
- package/tests/cypress/e2e/api/campaigns/campaigns-crud.cy.ts +592 -0
- package/tests/cypress/e2e/api/companies/companies-crud.cy.ts +682 -0
- package/tests/cypress/e2e/api/contacts/contacts-crud.cy.ts +668 -0
- package/tests/cypress/e2e/api/leads/leads-crud.cy.ts +648 -0
- package/tests/cypress/e2e/api/notes/notes-crud.cy.ts +424 -0
- package/tests/cypress/e2e/api/opportunities/opportunities-crud.cy.ts +865 -0
- package/tests/cypress/e2e/api/pipelines/pipelines-crud.cy.ts +545 -0
- package/tests/cypress/e2e/api/products/products-crud.cy.ts +447 -0
- package/tests/cypress/e2e/ui/activities/activities-admin.cy.ts +268 -0
- package/tests/cypress/e2e/ui/activities/activities-member.cy.ts +257 -0
- package/tests/cypress/e2e/ui/activities/activities-owner.cy.ts +268 -0
- package/tests/cypress/e2e/ui/companies/companies-admin.cy.ts +188 -0
- package/tests/cypress/e2e/ui/companies/companies-member.cy.ts +166 -0
- package/tests/cypress/e2e/ui/companies/companies-owner.cy.ts +189 -0
- package/tests/cypress/e2e/ui/contacts/contacts-admin.cy.ts +252 -0
- package/tests/cypress/e2e/ui/contacts/contacts-member.cy.ts +224 -0
- package/tests/cypress/e2e/ui/contacts/contacts-owner.cy.ts +236 -0
- package/tests/cypress/e2e/ui/leads/leads-admin.cy.ts +286 -0
- package/tests/cypress/e2e/ui/leads/leads-member.cy.ts +193 -0
- package/tests/cypress/e2e/ui/leads/leads-owner.cy.ts +210 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-admin.cy.ts +197 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-member.cy.ts +229 -0
- package/tests/cypress/e2e/ui/opportunities/opportunities-owner.cy.ts +196 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-admin.cy.ts +320 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-member.cy.ts +262 -0
- package/tests/cypress/e2e/ui/pipelines/pipelines-owner.cy.ts +282 -0
- package/tests/cypress/fixtures/blocks.json +9 -0
- package/tests/cypress/fixtures/entities.json +240 -0
- package/tests/cypress/src/components/CRMDataTable.js +223 -0
- package/tests/cypress/src/components/CRMMobileNav.js +138 -0
- package/tests/cypress/src/components/CRMSidebar.js +145 -0
- package/tests/cypress/src/components/CRMTopBar.js +194 -0
- package/tests/cypress/src/components/DealCard.js +197 -0
- package/tests/cypress/src/components/EntityDetail.ts +290 -0
- package/tests/cypress/src/components/EntityForm.ts +357 -0
- package/tests/cypress/src/components/EntityList.ts +360 -0
- package/tests/cypress/src/components/PipelineKanban.js +204 -0
- package/tests/cypress/src/components/StageColumn.js +196 -0
- package/tests/cypress/src/components/index.js +13 -0
- package/tests/cypress/src/components/index.ts +22 -0
- package/tests/cypress/src/controllers/ActivityAPIController.ts +113 -0
- package/tests/cypress/src/controllers/BaseAPIController.ts +307 -0
- package/tests/cypress/src/controllers/CampaignAPIController.ts +114 -0
- package/tests/cypress/src/controllers/CompanyAPIController.ts +112 -0
- package/tests/cypress/src/controllers/ContactAPIController.ts +104 -0
- package/tests/cypress/src/controllers/LeadAPIController.ts +96 -0
- package/tests/cypress/src/controllers/NoteAPIController.ts +130 -0
- package/tests/cypress/src/controllers/OpportunityAPIController.ts +134 -0
- package/tests/cypress/src/controllers/PipelineAPIController.ts +116 -0
- package/tests/cypress/src/controllers/ProductAPIController.ts +113 -0
- package/tests/cypress/src/controllers/index.ts +35 -0
- package/tests/cypress/src/entities/ActivitiesPOM.ts +130 -0
- package/tests/cypress/src/entities/CompaniesPOM.ts +117 -0
- package/tests/cypress/src/entities/ContactsPOM.ts +117 -0
- package/tests/cypress/src/entities/LeadsPOM.ts +129 -0
- package/tests/cypress/src/entities/OpportunitiesPOM.ts +178 -0
- package/tests/cypress/src/entities/PipelinesPOM.ts +341 -0
- package/tests/cypress/src/entities/index.ts +31 -0
- package/tests/cypress/src/forms/OpportunityForm.js +316 -0
- package/tests/cypress/src/forms/PipelineForm.js +243 -0
- package/tests/cypress/src/forms/index.js +8 -0
- package/tests/cypress/src/index.js +22 -0
- package/tests/cypress/src/index.ts +68 -0
- package/tests/cypress/src/selectors.ts +50 -0
- package/tests/cypress/src/session-helpers.ts +94 -0
- package/tests/cypress/support/e2e.ts +89 -0
- package/tests/cypress.config.ts +165 -0
- package/tests/tsconfig.json +15 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leads Page Object Model for CRM Theme
|
|
3
|
+
*
|
|
4
|
+
* Entity-specific POM for Leads.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const leads = new LeadsPOM()
|
|
8
|
+
* leads.list.validateTableVisible()
|
|
9
|
+
* leads.form.typeInField('firstName', 'John')
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { EntityList } from '../components/EntityList'
|
|
13
|
+
import { EntityForm } from '../components/EntityForm'
|
|
14
|
+
import { EntityDetail } from '../components/EntityDetail'
|
|
15
|
+
|
|
16
|
+
export class LeadsPOM {
|
|
17
|
+
/** Generic list POM for leads */
|
|
18
|
+
readonly list: EntityList
|
|
19
|
+
|
|
20
|
+
/** Generic form POM for leads */
|
|
21
|
+
readonly form: EntityForm
|
|
22
|
+
|
|
23
|
+
/** Generic detail POM for leads */
|
|
24
|
+
readonly detail: EntityDetail
|
|
25
|
+
|
|
26
|
+
/** Lead entity slug */
|
|
27
|
+
readonly slug = 'leads'
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
this.list = EntityList.for('leads')
|
|
31
|
+
this.form = EntityForm.for('leads')
|
|
32
|
+
this.detail = new EntityDetail('leads', 'lead', ['activities', 'notes'])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// NAVIGATION METHODS
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Visit the leads list page
|
|
41
|
+
*/
|
|
42
|
+
visitList() {
|
|
43
|
+
cy.visit('/dashboard/leads')
|
|
44
|
+
this.list.waitForPageLoad()
|
|
45
|
+
return this
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Visit lead detail page
|
|
50
|
+
*/
|
|
51
|
+
visitDetail(leadId: string) {
|
|
52
|
+
this.detail.visit(leadId)
|
|
53
|
+
return this
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Visit create lead page
|
|
58
|
+
*/
|
|
59
|
+
visitCreate() {
|
|
60
|
+
this.form.visitCreate()
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Visit edit lead page
|
|
66
|
+
*/
|
|
67
|
+
visitEdit(leadId: string) {
|
|
68
|
+
this.form.visitEdit(leadId)
|
|
69
|
+
return this
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================
|
|
73
|
+
// FORM HELPERS
|
|
74
|
+
// ============================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Fill lead form with common fields
|
|
78
|
+
*/
|
|
79
|
+
fillLeadForm(data: {
|
|
80
|
+
firstName?: string
|
|
81
|
+
lastName?: string
|
|
82
|
+
email?: string
|
|
83
|
+
phone?: string
|
|
84
|
+
company?: string
|
|
85
|
+
source?: string
|
|
86
|
+
status?: string
|
|
87
|
+
}) {
|
|
88
|
+
if (data.firstName) {
|
|
89
|
+
this.form.typeInField('firstName', data.firstName)
|
|
90
|
+
}
|
|
91
|
+
if (data.lastName) {
|
|
92
|
+
this.form.typeInField('lastName', data.lastName)
|
|
93
|
+
}
|
|
94
|
+
if (data.email) {
|
|
95
|
+
this.form.typeInField('email', data.email)
|
|
96
|
+
}
|
|
97
|
+
if (data.phone) {
|
|
98
|
+
this.form.typeInField('phone', data.phone)
|
|
99
|
+
}
|
|
100
|
+
if (data.company) {
|
|
101
|
+
this.form.typeInField('company', data.company)
|
|
102
|
+
}
|
|
103
|
+
if (data.source) {
|
|
104
|
+
this.form.selectOption('source', data.source)
|
|
105
|
+
}
|
|
106
|
+
if (data.status) {
|
|
107
|
+
this.form.selectOption('status', data.status)
|
|
108
|
+
}
|
|
109
|
+
return this
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Submit the lead form
|
|
114
|
+
*/
|
|
115
|
+
submitForm() {
|
|
116
|
+
this.form.submit()
|
|
117
|
+
return this
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert lead to contact
|
|
122
|
+
*/
|
|
123
|
+
convertToContact() {
|
|
124
|
+
cy.get('[data-cy="lead-convert-btn"]').click()
|
|
125
|
+
return this
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default LeadsPOM
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opportunities Page Object Model for CRM Theme
|
|
3
|
+
*
|
|
4
|
+
* Entity-specific POM for Opportunities (Deals).
|
|
5
|
+
* Includes support for both list view and kanban board integration.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const opportunities = new OpportunitiesPOM()
|
|
9
|
+
* opportunities.list.validateTableVisible()
|
|
10
|
+
* opportunities.validateDealCard('deal-123')
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { EntityList } from '../components/EntityList'
|
|
14
|
+
import { EntityForm } from '../components/EntityForm'
|
|
15
|
+
import { EntityDetail } from '../components/EntityDetail'
|
|
16
|
+
|
|
17
|
+
export class OpportunitiesPOM {
|
|
18
|
+
/** Generic list POM for opportunities */
|
|
19
|
+
readonly list: EntityList
|
|
20
|
+
|
|
21
|
+
/** Generic form POM for opportunities */
|
|
22
|
+
readonly form: EntityForm
|
|
23
|
+
|
|
24
|
+
/** Generic detail POM for opportunities */
|
|
25
|
+
readonly detail: EntityDetail
|
|
26
|
+
|
|
27
|
+
/** Opportunity entity slug */
|
|
28
|
+
readonly slug = 'opportunities'
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
this.list = EntityList.for('opportunities')
|
|
32
|
+
this.form = EntityForm.for('opportunities')
|
|
33
|
+
this.detail = new EntityDetail('opportunities', 'opportunity', ['activities', 'notes'])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// DEAL CARD SELECTORS (Kanban)
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Deal card selectors (used in Kanban board)
|
|
42
|
+
*/
|
|
43
|
+
get dealSelectors() {
|
|
44
|
+
return {
|
|
45
|
+
card: (dealId: string) => `[data-cy="deal-card-${dealId}"]`,
|
|
46
|
+
cardName: (dealId: string) => `[data-cy="deal-card-name-${dealId}"]`,
|
|
47
|
+
cardCompany: (dealId: string) => `[data-cy="deal-card-company-${dealId}"]`,
|
|
48
|
+
cardAmount: (dealId: string) => `[data-cy="deal-card-amount-${dealId}"]`,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================
|
|
53
|
+
// DEAL CARD METHODS
|
|
54
|
+
// ============================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Validate deal card is visible
|
|
58
|
+
*/
|
|
59
|
+
validateDealCardVisible(dealId: string) {
|
|
60
|
+
cy.get(this.dealSelectors.card(dealId)).should('be.visible')
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validate deal card displays correct name
|
|
66
|
+
*/
|
|
67
|
+
validateDealName(dealId: string, name: string) {
|
|
68
|
+
cy.get(this.dealSelectors.cardName(dealId)).should('contain.text', name)
|
|
69
|
+
return this
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate deal card displays correct company
|
|
74
|
+
*/
|
|
75
|
+
validateDealCompany(dealId: string, companyName: string) {
|
|
76
|
+
cy.get(this.dealSelectors.cardCompany(dealId)).should('contain.text', companyName)
|
|
77
|
+
return this
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validate deal card displays correct amount
|
|
82
|
+
*/
|
|
83
|
+
validateDealAmount(dealId: string, amount: string) {
|
|
84
|
+
cy.get(this.dealSelectors.cardAmount(dealId)).should('contain.text', amount)
|
|
85
|
+
return this
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Click on a deal card
|
|
90
|
+
*/
|
|
91
|
+
clickDealCard(dealId: string) {
|
|
92
|
+
cy.get(this.dealSelectors.card(dealId)).click()
|
|
93
|
+
return this
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============================================
|
|
97
|
+
// NAVIGATION METHODS
|
|
98
|
+
// ============================================
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Visit the opportunities list page
|
|
102
|
+
*/
|
|
103
|
+
visitList() {
|
|
104
|
+
cy.visit('/dashboard/opportunities')
|
|
105
|
+
this.list.waitForPageLoad()
|
|
106
|
+
return this
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Visit opportunity detail page
|
|
111
|
+
*/
|
|
112
|
+
visitDetail(opportunityId: string) {
|
|
113
|
+
this.detail.visit(opportunityId)
|
|
114
|
+
return this
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Visit create opportunity page
|
|
119
|
+
*/
|
|
120
|
+
visitCreate() {
|
|
121
|
+
this.form.visitCreate()
|
|
122
|
+
return this
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Visit edit opportunity page
|
|
127
|
+
*/
|
|
128
|
+
visitEdit(opportunityId: string) {
|
|
129
|
+
this.form.visitEdit(opportunityId)
|
|
130
|
+
return this
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// FORM HELPERS
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Fill opportunity form with common fields
|
|
139
|
+
*/
|
|
140
|
+
fillOpportunityForm(data: {
|
|
141
|
+
name: string
|
|
142
|
+
amount?: string
|
|
143
|
+
probability?: string
|
|
144
|
+
company?: string
|
|
145
|
+
contact?: string
|
|
146
|
+
stage?: string
|
|
147
|
+
}) {
|
|
148
|
+
if (data.name) {
|
|
149
|
+
this.form.typeInField('name', data.name)
|
|
150
|
+
}
|
|
151
|
+
if (data.amount) {
|
|
152
|
+
this.form.typeInField('amount', data.amount)
|
|
153
|
+
}
|
|
154
|
+
if (data.probability) {
|
|
155
|
+
this.form.typeInField('probability', data.probability)
|
|
156
|
+
}
|
|
157
|
+
if (data.company) {
|
|
158
|
+
this.form.selectOption('companyId', data.company)
|
|
159
|
+
}
|
|
160
|
+
if (data.contact) {
|
|
161
|
+
this.form.selectOption('contactId', data.contact)
|
|
162
|
+
}
|
|
163
|
+
if (data.stage) {
|
|
164
|
+
this.form.selectOption('stageId', data.stage)
|
|
165
|
+
}
|
|
166
|
+
return this
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Submit the opportunity form
|
|
171
|
+
*/
|
|
172
|
+
submitForm() {
|
|
173
|
+
this.form.submit()
|
|
174
|
+
return this
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export default OpportunitiesPOM
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipelines Page Object Model for CRM Theme
|
|
3
|
+
*
|
|
4
|
+
* Entity-specific POM for Pipelines with Kanban board support.
|
|
5
|
+
* Extends generic patterns with pipeline-specific functionality.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const pipelines = new PipelinesPOM()
|
|
9
|
+
* pipelines.kanban.validateBoardVisible()
|
|
10
|
+
* pipelines.form.fillStages([{ name: 'Lead', probability: 10 }])
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { EntityList } from '../components/EntityList'
|
|
14
|
+
import { EntityForm } from '../components/EntityForm'
|
|
15
|
+
import { EntityDetail } from '../components/EntityDetail'
|
|
16
|
+
|
|
17
|
+
export interface StageInput {
|
|
18
|
+
name: string
|
|
19
|
+
probability: number
|
|
20
|
+
color?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class PipelinesPOM {
|
|
24
|
+
/** Generic list POM for pipelines */
|
|
25
|
+
readonly list: EntityList
|
|
26
|
+
|
|
27
|
+
/** Generic form POM for pipelines */
|
|
28
|
+
readonly form: EntityForm
|
|
29
|
+
|
|
30
|
+
/** Generic detail POM for pipelines */
|
|
31
|
+
readonly detail: EntityDetail
|
|
32
|
+
|
|
33
|
+
/** Pipeline entity slug */
|
|
34
|
+
readonly slug = 'pipelines'
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
this.list = EntityList.for('pipelines')
|
|
38
|
+
this.form = EntityForm.for('pipelines')
|
|
39
|
+
this.detail = new EntityDetail('pipelines', 'pipeline', ['stages'])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ============================================
|
|
43
|
+
// PIPELINE FORM SELECTORS (Custom)
|
|
44
|
+
// ============================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pipeline-specific form selectors
|
|
48
|
+
*/
|
|
49
|
+
get formSelectors() {
|
|
50
|
+
return {
|
|
51
|
+
form: '[data-cy="pipeline-form"]',
|
|
52
|
+
nameInput: '[data-cy="pipeline-form-name"]',
|
|
53
|
+
descriptionInput: '[data-cy="pipeline-form-description"]',
|
|
54
|
+
cancelButton: '[data-cy="pipeline-form-cancel"]',
|
|
55
|
+
submitButton: '[data-cy="pipeline-form-submit"]',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stages repeater selectors
|
|
61
|
+
*/
|
|
62
|
+
get stagesSelectors() {
|
|
63
|
+
return {
|
|
64
|
+
repeater: '[data-cy="stages-repeater"]',
|
|
65
|
+
list: '[data-cy="stages-repeater-list"]',
|
|
66
|
+
count: '[data-cy="stages-repeater-count"]',
|
|
67
|
+
addButton: '[data-cy="stages-repeater-add-btn"]',
|
|
68
|
+
item: (stageId: string) => `[data-cy="stages-repeater-item-${stageId}"]`,
|
|
69
|
+
nameInput: (stageId: string) => `[data-cy="stages-repeater-name-${stageId}"]`,
|
|
70
|
+
probabilityInput: (stageId: string) => `[data-cy="stages-repeater-probability-${stageId}"]`,
|
|
71
|
+
deleteButton: (stageId: string) => `[data-cy="stages-repeater-delete-${stageId}"]`,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================
|
|
76
|
+
// KANBAN BOARD SELECTORS
|
|
77
|
+
// ============================================
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Kanban board selectors
|
|
81
|
+
*/
|
|
82
|
+
get kanbanSelectors() {
|
|
83
|
+
return {
|
|
84
|
+
board: '[data-cy="pipeline-kanban"]',
|
|
85
|
+
header: '[data-cy="pipeline-kanban-header"]',
|
|
86
|
+
stats: '[data-cy="pipeline-kanban-stats"]',
|
|
87
|
+
boardContainer: '[data-cy="pipeline-kanban-board"]',
|
|
88
|
+
addDealButton: '[data-cy="pipeline-kanban-add-deal-btn"]',
|
|
89
|
+
// Stage columns
|
|
90
|
+
stageColumn: (stageId: string) => `[data-cy="stage-column-${stageId}"]`,
|
|
91
|
+
stageHeader: (stageId: string) => `[data-cy="stage-column-header-${stageId}"]`,
|
|
92
|
+
stageDeals: (stageId: string) => `[data-cy="stage-column-deals-${stageId}"]`,
|
|
93
|
+
stageEmpty: (stageId: string) => `[data-cy="stage-column-empty-${stageId}"]`,
|
|
94
|
+
stageAddDeal: (stageId: string) => `[data-cy="stage-column-add-deal-${stageId}"]`,
|
|
95
|
+
// Deal cards
|
|
96
|
+
dealCard: (dealId: string) => `[data-cy="deal-card-${dealId}"]`,
|
|
97
|
+
dealCardName: (dealId: string) => `[data-cy="deal-card-name-${dealId}"]`,
|
|
98
|
+
dealCardCompany: (dealId: string) => `[data-cy="deal-card-company-${dealId}"]`,
|
|
99
|
+
dealCardAmount: (dealId: string) => `[data-cy="deal-card-amount-${dealId}"]`,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================
|
|
104
|
+
// PIPELINE FORM METHODS
|
|
105
|
+
// ============================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fill the pipeline form with basic info
|
|
109
|
+
*/
|
|
110
|
+
fillPipelineForm(data: { name: string; description?: string }) {
|
|
111
|
+
cy.get(this.formSelectors.nameInput).clear().type(data.name)
|
|
112
|
+
if (data.description) {
|
|
113
|
+
cy.get(this.formSelectors.descriptionInput).clear().type(data.description)
|
|
114
|
+
}
|
|
115
|
+
return this
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Add a new stage to the pipeline
|
|
120
|
+
*/
|
|
121
|
+
addStage() {
|
|
122
|
+
cy.get(this.stagesSelectors.addButton).click()
|
|
123
|
+
return this
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Fill stage data by index
|
|
128
|
+
*/
|
|
129
|
+
fillStageByIndex(index: number, data: { name: string; probability: number }) {
|
|
130
|
+
cy.get(this.stagesSelectors.list)
|
|
131
|
+
.find('[data-cy^="stages-repeater-item-"]')
|
|
132
|
+
.eq(index)
|
|
133
|
+
.within(() => {
|
|
134
|
+
cy.get('input[data-cy^="stages-repeater-name-"]').clear().type(data.name)
|
|
135
|
+
cy.get('input[data-cy^="stages-repeater-probability-"]').clear().type(data.probability.toString())
|
|
136
|
+
})
|
|
137
|
+
return this
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Delete stage by index
|
|
142
|
+
*/
|
|
143
|
+
deleteStageByIndex(index: number) {
|
|
144
|
+
cy.get(this.stagesSelectors.list)
|
|
145
|
+
.find('[data-cy^="stages-repeater-item-"]')
|
|
146
|
+
.eq(index)
|
|
147
|
+
.find('[data-cy^="stages-repeater-delete-"]')
|
|
148
|
+
.click()
|
|
149
|
+
return this
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Submit the pipeline form
|
|
154
|
+
*/
|
|
155
|
+
submitPipelineForm() {
|
|
156
|
+
cy.get(this.formSelectors.submitButton).click()
|
|
157
|
+
return this
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Cancel the pipeline form
|
|
162
|
+
*/
|
|
163
|
+
cancelPipelineForm() {
|
|
164
|
+
cy.get(this.formSelectors.cancelButton).click()
|
|
165
|
+
return this
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================
|
|
169
|
+
// KANBAN BOARD METHODS
|
|
170
|
+
// ============================================
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validate the kanban board is visible
|
|
174
|
+
*/
|
|
175
|
+
validateKanbanVisible() {
|
|
176
|
+
cy.get(this.kanbanSelectors.board).should('be.visible')
|
|
177
|
+
return this
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Validate kanban header is visible with pipeline name
|
|
182
|
+
*/
|
|
183
|
+
validateKanbanHeader(pipelineName: string) {
|
|
184
|
+
cy.get(this.kanbanSelectors.header)
|
|
185
|
+
.should('be.visible')
|
|
186
|
+
.and('contain.text', pipelineName)
|
|
187
|
+
return this
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Validate stats cards are visible
|
|
192
|
+
*/
|
|
193
|
+
validateStatsVisible() {
|
|
194
|
+
cy.get(this.kanbanSelectors.stats).should('be.visible')
|
|
195
|
+
return this
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Validate a stage column exists
|
|
200
|
+
*/
|
|
201
|
+
validateStageColumnVisible(stageId: string) {
|
|
202
|
+
cy.get(this.kanbanSelectors.stageColumn(stageId)).should('be.visible')
|
|
203
|
+
return this
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Validate stage column is empty
|
|
208
|
+
*/
|
|
209
|
+
validateStageEmpty(stageId: string) {
|
|
210
|
+
cy.get(this.kanbanSelectors.stageEmpty(stageId)).should('be.visible')
|
|
211
|
+
return this
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Validate deal card exists in stage
|
|
216
|
+
*/
|
|
217
|
+
validateDealInStage(dealId: string, stageId: string) {
|
|
218
|
+
cy.get(this.kanbanSelectors.stageDeals(stageId))
|
|
219
|
+
.find(this.kanbanSelectors.dealCard(dealId))
|
|
220
|
+
.should('exist')
|
|
221
|
+
return this
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Click on a deal card
|
|
226
|
+
*/
|
|
227
|
+
clickDealCard(dealId: string) {
|
|
228
|
+
cy.get(this.kanbanSelectors.dealCard(dealId)).click()
|
|
229
|
+
return this
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Click add deal button in header
|
|
234
|
+
*/
|
|
235
|
+
clickAddDeal() {
|
|
236
|
+
cy.get(this.kanbanSelectors.addDealButton).click()
|
|
237
|
+
return this
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Click add deal in specific stage
|
|
242
|
+
*/
|
|
243
|
+
clickAddDealInStage(stageId: string) {
|
|
244
|
+
cy.get(this.kanbanSelectors.stageAddDeal(stageId)).click()
|
|
245
|
+
return this
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Drag deal from one stage to another
|
|
250
|
+
* Note: Uses HTML5 drag and drop simulation
|
|
251
|
+
*/
|
|
252
|
+
dragDealToStage(dealId: string, targetStageId: string) {
|
|
253
|
+
const dealCard = this.kanbanSelectors.dealCard(dealId)
|
|
254
|
+
const targetColumn = this.kanbanSelectors.stageDeals(targetStageId)
|
|
255
|
+
|
|
256
|
+
cy.get(dealCard).trigger('dragstart', { dataTransfer: new DataTransfer() })
|
|
257
|
+
cy.get(targetColumn).trigger('dragover')
|
|
258
|
+
cy.get(targetColumn).trigger('drop')
|
|
259
|
+
cy.get(dealCard).trigger('dragend')
|
|
260
|
+
|
|
261
|
+
return this
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ============================================
|
|
265
|
+
// NAVIGATION METHODS
|
|
266
|
+
// ============================================
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Visit the pipelines list page
|
|
270
|
+
*/
|
|
271
|
+
visitList() {
|
|
272
|
+
cy.visit('/dashboard/pipelines')
|
|
273
|
+
this.list.waitForPageLoad()
|
|
274
|
+
return this
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Visit a specific pipeline's kanban view
|
|
279
|
+
*/
|
|
280
|
+
visitKanban(pipelineId: string) {
|
|
281
|
+
cy.visit(`/dashboard/pipelines/${pipelineId}`)
|
|
282
|
+
this.validateKanbanVisible()
|
|
283
|
+
return this
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Visit the create pipeline page
|
|
288
|
+
*/
|
|
289
|
+
visitCreate() {
|
|
290
|
+
cy.visit('/dashboard/pipelines/create')
|
|
291
|
+
cy.get(this.formSelectors.form).should('be.visible')
|
|
292
|
+
return this
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Visit the edit pipeline page
|
|
297
|
+
*/
|
|
298
|
+
visitEdit(pipelineId: string) {
|
|
299
|
+
cy.visit(`/dashboard/pipelines/${pipelineId}/edit`)
|
|
300
|
+
cy.get(this.formSelectors.form).should('be.visible')
|
|
301
|
+
return this
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ============================================
|
|
305
|
+
// VALIDATION METHODS
|
|
306
|
+
// ============================================
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Validate pipeline form is visible
|
|
310
|
+
*/
|
|
311
|
+
validateFormVisible() {
|
|
312
|
+
cy.get(this.formSelectors.form).should('be.visible')
|
|
313
|
+
return this
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Validate stages repeater is visible
|
|
318
|
+
*/
|
|
319
|
+
validateStagesRepeaterVisible() {
|
|
320
|
+
cy.get(this.stagesSelectors.repeater).should('be.visible')
|
|
321
|
+
return this
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Validate stages count
|
|
326
|
+
*/
|
|
327
|
+
validateStagesCount(count: number) {
|
|
328
|
+
cy.get(this.stagesSelectors.count).should('contain.text', `${count} stage`)
|
|
329
|
+
return this
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Validate deal card displays correct amount
|
|
334
|
+
*/
|
|
335
|
+
validateDealAmount(dealId: string, amount: string) {
|
|
336
|
+
cy.get(this.kanbanSelectors.dealCardAmount(dealId)).should('contain.text', amount)
|
|
337
|
+
return this
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export default PipelinesPOM
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRM Theme - Entity POMs
|
|
3
|
+
*
|
|
4
|
+
* Export all entity-specific Page Object Models for CRM theme testing.
|
|
5
|
+
* Each POM combines generic components (EntityList, EntityForm, EntityDetail)
|
|
6
|
+
* with entity-specific functionality.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { PipelinesPOM } from './PipelinesPOM'
|
|
10
|
+
export { OpportunitiesPOM } from './OpportunitiesPOM'
|
|
11
|
+
export { LeadsPOM } from './LeadsPOM'
|
|
12
|
+
export { ContactsPOM } from './ContactsPOM'
|
|
13
|
+
export { CompaniesPOM } from './CompaniesPOM'
|
|
14
|
+
export { ActivitiesPOM } from './ActivitiesPOM'
|
|
15
|
+
|
|
16
|
+
// Default exports for convenience
|
|
17
|
+
import { PipelinesPOM } from './PipelinesPOM'
|
|
18
|
+
import { OpportunitiesPOM } from './OpportunitiesPOM'
|
|
19
|
+
import { LeadsPOM } from './LeadsPOM'
|
|
20
|
+
import { ContactsPOM } from './ContactsPOM'
|
|
21
|
+
import { CompaniesPOM } from './CompaniesPOM'
|
|
22
|
+
import { ActivitiesPOM } from './ActivitiesPOM'
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
PipelinesPOM,
|
|
26
|
+
OpportunitiesPOM,
|
|
27
|
+
LeadsPOM,
|
|
28
|
+
ContactsPOM,
|
|
29
|
+
CompaniesPOM,
|
|
30
|
+
ActivitiesPOM,
|
|
31
|
+
}
|