]>
Commit | Line | Data |
---|---|---|
e306af50 TL |
1 | interface Page { |
2 | url: string; | |
3 | id: string; | |
4 | } | |
5 | ||
6 | export abstract class PageHelper { | |
7 | pages: Record<string, Page>; | |
8 | ||
9 | /** | |
10 | * Decorator to be used on Helper methods to restrict access to one particular URL. This shall | |
11 | * help developers to prevent and highlight mistakes. It also reduces boilerplate code and by | |
12 | * thus, increases readability. | |
13 | */ | |
14 | static restrictTo(page: string): Function { | |
15 | return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { | |
16 | const fn: Function = descriptor.value; | |
17 | descriptor.value = function (...args: any) { | |
18 | cy.location('hash').should((url) => { | |
19 | expect(url).to.eq( | |
20 | page, | |
21 | `Method ${target.constructor.name}::${propertyKey} is supposed to be ` + | |
22 | `run on path "${page}", but was run on URL "${url}"` | |
23 | ); | |
24 | }); | |
25 | fn.apply(this, args); | |
26 | }; | |
27 | }; | |
28 | } | |
29 | ||
30 | /** | |
31 | * Navigates to the given page or to index. | |
32 | * Waits until the page component is loaded | |
33 | */ | |
34 | navigateTo(name: string = null) { | |
35 | name = name || 'index'; | |
36 | const page = this.pages[name]; | |
37 | ||
38 | cy.visit(page.url); | |
39 | cy.get(page.id); | |
40 | } | |
41 | ||
42 | /** | |
43 | * Navigates back and waits for the hash to change | |
44 | */ | |
45 | navigateBack() { | |
46 | cy.location('hash').then((hash) => { | |
47 | cy.go('back'); | |
48 | cy.location('hash').should('not.be', hash); | |
49 | }); | |
50 | } | |
51 | ||
52 | /** | |
53 | * Navigates to the edit page | |
54 | */ | |
a4b75251 | 55 | navigateEdit(name: string, select = true, breadcrumb = true) { |
e306af50 TL |
56 | if (select) { |
57 | this.navigateTo(); | |
58 | this.getFirstTableCell(name).click(); | |
59 | } | |
f67539c2 | 60 | cy.contains('Creating...').should('not.exist'); |
e306af50 | 61 | cy.contains('button', 'Edit').click(); |
a4b75251 TL |
62 | if (breadcrumb) { |
63 | this.expectBreadcrumbText('Edit'); | |
64 | } | |
e306af50 TL |
65 | } |
66 | ||
67 | /** | |
68 | * Checks the active breadcrumb value. | |
69 | */ | |
70 | expectBreadcrumbText(text: string) { | |
71 | cy.get('.breadcrumb-item.active').should('have.text', text); | |
72 | } | |
73 | ||
f67539c2 | 74 | getTabs() { |
39ae355f | 75 | return cy.get('.nav.nav-tabs a'); |
f67539c2 TL |
76 | } |
77 | ||
78 | getTab(tabName: string) { | |
39ae355f | 79 | return cy.contains('.nav.nav-tabs a', tabName); |
f67539c2 TL |
80 | } |
81 | ||
e306af50 | 82 | getTabText(index: number) { |
f67539c2 | 83 | return this.getTabs().its(index).text(); |
e306af50 TL |
84 | } |
85 | ||
86 | getTabsCount(): any { | |
f67539c2 | 87 | return this.getTabs().its('length'); |
e306af50 TL |
88 | } |
89 | ||
a4b75251 TL |
90 | /** |
91 | * Helper method to navigate/click a tab inside the expanded table row. | |
92 | * @param selector The selector of the expanded table row. | |
93 | * @param name The name of the row which should expand. | |
94 | * @param tabName Name of the tab to be navigated/clicked. | |
95 | */ | |
96 | clickTab(selector: string, name: string, tabName: string) { | |
97 | this.getExpandCollapseElement(name).click(); | |
98 | cy.get(selector).within(() => { | |
99 | this.getTab(tabName).click(); | |
100 | }); | |
101 | } | |
102 | ||
e306af50 TL |
103 | /** |
104 | * Helper method to select an option inside a select element. | |
105 | * This method will also expect that the option was set. | |
106 | * @param option The option text (not value) to be selected. | |
107 | */ | |
108 | selectOption(selectionName: string, option: string) { | |
109 | cy.get(`select[name=${selectionName}]`).select(option); | |
110 | return this.expectSelectOption(selectionName, option); | |
111 | } | |
112 | ||
113 | /** | |
114 | * Helper method to expect a set option inside a select element. | |
115 | * @param option The selected option text (not value) that is to | |
116 | * be expected. | |
117 | */ | |
118 | expectSelectOption(selectionName: string, option: string) { | |
119 | return cy.get(`select[name=${selectionName}] option:checked`).contains(option); | |
120 | } | |
121 | ||
122 | getLegends() { | |
123 | return cy.get('legend'); | |
124 | } | |
125 | ||
126 | getToast() { | |
127 | return cy.get('.ngx-toastr'); | |
128 | } | |
129 | ||
130 | /** | |
131 | * Waits for the table to load its data | |
132 | * Should be used in all methods that access the datatable | |
133 | */ | |
134 | private waitDataTableToLoad() { | |
135 | cy.get('cd-table').should('exist'); | |
136 | cy.get('datatable-scroller, .empty-row'); | |
137 | } | |
138 | ||
139 | getDataTables() { | |
140 | this.waitDataTableToLoad(); | |
141 | ||
142 | return cy.get('cd-table .dataTables_wrapper'); | |
143 | } | |
144 | ||
f67539c2 TL |
145 | private getTableCountSpan(spanType: 'selected' | 'found' | 'total') { |
146 | return cy.contains('.datatable-footer-inner .page-count span', spanType); | |
e306af50 TL |
147 | } |
148 | ||
f67539c2 TL |
149 | // Get 'selected', 'found', or 'total' row count of a table. |
150 | getTableCount(spanType: 'selected' | 'found' | 'total') { | |
e306af50 | 151 | this.waitDataTableToLoad(); |
f67539c2 | 152 | return this.getTableCountSpan(spanType).then(($elem) => { |
e306af50 | 153 | const text = $elem |
f67539c2 | 154 | .filter((_i, e) => e.innerText.includes(spanType)) |
e306af50 TL |
155 | .first() |
156 | .text(); | |
157 | ||
f67539c2 | 158 | return Number(text.match(/(\d+)\s+\w*/)[1]); |
e306af50 TL |
159 | }); |
160 | } | |
161 | ||
f67539c2 TL |
162 | // Wait until selected', 'found', or 'total' row count of a table equal to a number. |
163 | expectTableCount(spanType: 'selected' | 'found' | 'total', count: number) { | |
e306af50 | 164 | this.waitDataTableToLoad(); |
f67539c2 TL |
165 | this.getTableCountSpan(spanType).should(($elem) => { |
166 | const text = $elem.first().text(); | |
167 | expect(Number(text.match(/(\d+)\s+\w*/)[1])).to.equal(count); | |
e306af50 TL |
168 | }); |
169 | } | |
170 | ||
171 | getTableRow(content: string) { | |
172 | this.waitDataTableToLoad(); | |
173 | ||
20effc67 | 174 | this.searchTable(content); |
e306af50 TL |
175 | return cy.contains('.datatable-body-row', content); |
176 | } | |
177 | ||
178 | getTableRows() { | |
179 | this.waitDataTableToLoad(); | |
180 | ||
181 | return cy.get('datatable-row-wrapper'); | |
182 | } | |
183 | ||
184 | /** | |
185 | * Returns the first table cell. | |
186 | * Optionally, you can specify the content of the cell. | |
187 | */ | |
188 | getFirstTableCell(content?: string) { | |
189 | this.waitDataTableToLoad(); | |
190 | ||
191 | if (content) { | |
20effc67 | 192 | this.searchTable(content); |
e306af50 TL |
193 | return cy.contains('.datatable-body-cell-label', content); |
194 | } else { | |
195 | return cy.get('.datatable-body-cell-label').first(); | |
196 | } | |
197 | } | |
198 | ||
33c7a0ef | 199 | getTableCell(columnIndex: number, exactContent: string, partialMatch = false) { |
f67539c2 | 200 | this.waitDataTableToLoad(); |
20effc67 TL |
201 | this.clearTableSearchInput(); |
202 | this.searchTable(exactContent); | |
33c7a0ef TL |
203 | if (partialMatch) { |
204 | return cy.contains( | |
205 | `datatable-body-row datatable-body-cell:nth-child(${columnIndex})`, | |
206 | exactContent | |
207 | ); | |
208 | } | |
f67539c2 TL |
209 | return cy.contains( |
210 | `datatable-body-row datatable-body-cell:nth-child(${columnIndex})`, | |
211 | new RegExp(`^${exactContent}$`) | |
212 | ); | |
213 | } | |
214 | ||
a4b75251 TL |
215 | existTableCell(name: string, oughtToBePresent = true) { |
216 | const waitRule = oughtToBePresent ? 'be.visible' : 'not.exist'; | |
217 | this.getFirstTableCell(name).should(waitRule); | |
218 | } | |
219 | ||
e306af50 TL |
220 | getExpandCollapseElement(content?: string) { |
221 | this.waitDataTableToLoad(); | |
222 | ||
223 | if (content) { | |
224 | return cy.contains('.datatable-body-row', content).find('.tc_expand-collapse'); | |
225 | } else { | |
226 | return cy.get('.tc_expand-collapse').first(); | |
227 | } | |
228 | } | |
f67539c2 | 229 | |
e306af50 TL |
230 | /** |
231 | * Gets column headers of table | |
232 | */ | |
233 | getDataTableHeaders(index = 0) { | |
234 | this.waitDataTableToLoad(); | |
235 | ||
a4b75251 | 236 | return cy.get('.datatable-header').its(index).find('.datatable-header-cell'); |
e306af50 TL |
237 | } |
238 | ||
239 | /** | |
240 | * Grabs striped tables | |
241 | */ | |
242 | getStatusTables() { | |
243 | return cy.get('.table.table-striped'); | |
244 | } | |
245 | ||
246 | filterTable(name: string, option: string) { | |
247 | this.waitDataTableToLoad(); | |
248 | ||
f67539c2 | 249 | cy.get('.tc_filter_name > button').click(); |
e306af50 TL |
250 | cy.contains(`.tc_filter_name .dropdown-item`, name).click(); |
251 | ||
f67539c2 | 252 | cy.get('.tc_filter_option > button').click(); |
e306af50 TL |
253 | cy.contains(`.tc_filter_option .dropdown-item`, option).click(); |
254 | } | |
255 | ||
f67539c2 TL |
256 | setPageSize(size: string) { |
257 | cy.get('cd-table .dataTables_paginate input').first().clear({ force: true }).type(size); | |
258 | } | |
259 | ||
20effc67 | 260 | searchTable(text: string) { |
e306af50 TL |
261 | this.waitDataTableToLoad(); |
262 | ||
f67539c2 | 263 | this.setPageSize('10'); |
39ae355f | 264 | cy.get('[aria-label=search]').first().clear({ force: true }).type(text); |
e306af50 TL |
265 | } |
266 | ||
267 | clearTableSearchInput() { | |
268 | this.waitDataTableToLoad(); | |
269 | ||
20effc67 | 270 | return cy.get('cd-table .search button').first().click(); |
e306af50 TL |
271 | } |
272 | ||
f67539c2 TL |
273 | // Click the action button |
274 | clickActionButton(action: string) { | |
275 | cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu | |
276 | cy.get(`button.${action}`).click(); // click on "action" menu item | |
277 | } | |
278 | ||
e306af50 TL |
279 | /** |
280 | * This is a generic method to delete table rows. | |
281 | * It will select the first row that contains the provided name and delete it. | |
282 | * After that it will wait until the row is no longer displayed. | |
f67539c2 TL |
283 | * @param name The string to search in table cells. |
284 | * @param columnIndex If provided, search string in columnIndex column. | |
e306af50 | 285 | */ |
a4b75251 | 286 | delete(name: string, columnIndex?: number, section?: string) { |
e306af50 | 287 | // Selects row |
f67539c2 TL |
288 | const getRow = columnIndex |
289 | ? this.getTableCell.bind(this, columnIndex) | |
290 | : this.getFirstTableCell.bind(this); | |
291 | getRow(name).click(); | |
a4b75251 TL |
292 | let action: string; |
293 | section === 'hosts' ? (action = 'remove') : (action = 'delete'); | |
e306af50 | 294 | |
a4b75251 TL |
295 | // Clicks on table Delete/Remove button |
296 | this.clickActionButton(action); | |
e306af50 | 297 | |
a4b75251 TL |
298 | // Convert action to SentenceCase and Confirms deletion |
299 | const actionUpperCase = action.charAt(0).toUpperCase() + action.slice(1); | |
f67539c2 | 300 | cy.get('cd-modal .custom-control-label').click(); |
a4b75251 | 301 | cy.contains('cd-modal button', actionUpperCase).click(); |
e306af50 TL |
302 | |
303 | // Wait for modal to close | |
304 | cy.get('cd-modal').should('not.exist'); | |
305 | ||
306 | // Waits for item to be removed from table | |
f67539c2 | 307 | getRow(name).should('not.exist'); |
e306af50 TL |
308 | } |
309 | } |