]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts
import ceph quincy 17.2.6
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / cypress / integration / page-helper.po.ts
CommitLineData
e306af50
TL
1interface Page {
2 url: string;
3 id: string;
4}
5
6export 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}