@thinkwise/testwise 0.0.2-alpha.2
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/.ci/azure-pipelines.yaml +80 -0
- package/.eslintcache +1 -0
- package/.gitattributes +3 -0
- package/.gitconfig +2 -0
- package/.vscode/settings.json +18 -0
- package/Testwise.ts +22 -0
- package/components/BaseComponent.ts +95 -0
- package/components/BaseComponentObjects.ts +10 -0
- package/components/IComponentObjects.ts +5 -0
- package/components/Splitter.ts +11 -0
- package/components/TSFComponent.ts +8 -0
- package/components/actionbar/Actionbar.ts +57 -0
- package/components/actionbar/ActionbarObjects.ts +67 -0
- package/components/export/ExportComponent.ts +70 -0
- package/components/export/ExportComponentObjects.ts +23 -0
- package/components/filter/FilterForm.ts +11 -0
- package/components/filter/FindForm.ts +11 -0
- package/components/form/Form.ts +31 -0
- package/components/grid/Grid.ts +221 -0
- package/components/grid/GridObjects.ts +45 -0
- package/components/index.ts +6 -0
- package/components/pop-up/PopUpComponent.ts +14 -0
- package/components/pop-up/PopUpComponentModels.ts +13 -0
- package/components/tab/Tab.ts +29 -0
- package/components/task/TaskBar.ts +11 -0
- package/components/task/TaskTiles.ts +11 -0
- package/config.json +10 -0
- package/dist/Testwise.d.ts +2 -0
- package/dist/Testwise.js +14 -0
- package/dist/Testwise.js.map +1 -0
- package/dist/components/BaseComponent.d.ts +29 -0
- package/dist/components/BaseComponent.js +67 -0
- package/dist/components/BaseComponent.js.map +1 -0
- package/dist/components/BaseComponentObjects.d.ts +6 -0
- package/dist/components/BaseComponentObjects.js +6 -0
- package/dist/components/BaseComponentObjects.js.map +1 -0
- package/dist/components/IComponentObjects.d.ts +4 -0
- package/dist/components/IComponentObjects.js +2 -0
- package/dist/components/IComponentObjects.js.map +1 -0
- package/dist/components/Splitter.d.ts +5 -0
- package/dist/components/Splitter.js +10 -0
- package/dist/components/Splitter.js.map +1 -0
- package/dist/components/TSFComponent.d.ts +6 -0
- package/dist/components/TSFComponent.js +2 -0
- package/dist/components/TSFComponent.js.map +1 -0
- package/dist/components/actionbar/Actionbar.d.ts +27 -0
- package/dist/components/actionbar/Actionbar.js +35 -0
- package/dist/components/actionbar/Actionbar.js.map +1 -0
- package/dist/components/actionbar/ActionbarObjects.d.ts +24 -0
- package/dist/components/actionbar/ActionbarObjects.js +30 -0
- package/dist/components/actionbar/ActionbarObjects.js.map +1 -0
- package/dist/components/export/ExportComponent.d.ts +12 -0
- package/dist/components/export/ExportComponent.js +55 -0
- package/dist/components/export/ExportComponent.js.map +1 -0
- package/dist/components/export/ExportComponentObjects.d.ts +11 -0
- package/dist/components/export/ExportComponentObjects.js +12 -0
- package/dist/components/export/ExportComponentObjects.js.map +1 -0
- package/dist/components/form/Form.d.ts +7 -0
- package/dist/components/form/Form.js +31 -0
- package/dist/components/form/Form.js.map +1 -0
- package/dist/components/grid/Grid.d.ts +93 -0
- package/dist/components/grid/Grid.js +183 -0
- package/dist/components/grid/Grid.js.map +1 -0
- package/dist/components/grid/GridObjects.d.ts +20 -0
- package/dist/components/grid/GridObjects.js +25 -0
- package/dist/components/grid/GridObjects.js.map +1 -0
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.js +7 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/pop-up/PopUpComponent.d.ts +7 -0
- package/dist/components/pop-up/PopUpComponent.js +9 -0
- package/dist/components/pop-up/PopUpComponent.js.map +1 -0
- package/dist/components/pop-up/PopUpComponentModels.d.ts +7 -0
- package/dist/components/pop-up/PopUpComponentModels.js +8 -0
- package/dist/components/pop-up/PopUpComponentModels.js.map +1 -0
- package/dist/config.json +10 -0
- package/dist/enums/ActionbarRegions.d.ts +5 -0
- package/dist/enums/ActionbarRegions.js +7 -0
- package/dist/enums/ActionbarRegions.js.map +1 -0
- package/dist/enums/ButtonEnums.d.ts +24 -0
- package/dist/enums/ButtonEnums.js +29 -0
- package/dist/enums/ButtonEnums.js.map +1 -0
- package/dist/enums/ExportFormat.d.ts +5 -0
- package/dist/enums/ExportFormat.js +7 -0
- package/dist/enums/ExportFormat.js.map +1 -0
- package/dist/enums/LogLevel.d.ts +8 -0
- package/dist/enums/LogLevel.js +11 -0
- package/dist/enums/LogLevel.js.map +1 -0
- package/dist/example-code/TestifyService.d.ts +22 -0
- package/dist/example-code/TestifyService.js +191 -0
- package/dist/example-code/TestifyService.js.map +1 -0
- package/dist/helpers/InflightRequestTracker.d.ts +10 -0
- package/dist/helpers/InflightRequestTracker.js +26 -0
- package/dist/helpers/InflightRequestTracker.js.map +1 -0
- package/dist/helpers/LoginHelper.d.ts +38 -0
- package/dist/helpers/LoginHelper.js +112 -0
- package/dist/helpers/LoginHelper.js.map +1 -0
- package/dist/helpers/UserSimulationHelper.d.ts +20 -0
- package/dist/helpers/UserSimulationHelper.js +62 -0
- package/dist/helpers/UserSimulationHelper.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/page-extensions/GoToDeepLink.d.ts +11 -0
- package/dist/page-extensions/GoToDeepLink.js +17 -0
- package/dist/page-extensions/GoToDeepLink.js.map +1 -0
- package/dist/page-extensions/LoginFeatures.d.ts +16 -0
- package/dist/page-extensions/LoginFeatures.js +30 -0
- package/dist/page-extensions/LoginFeatures.js.map +1 -0
- package/dist/page-extensions/UserSimulation.d.ts +15 -0
- package/dist/page-extensions/UserSimulation.js +30 -0
- package/dist/page-extensions/UserSimulation.js.map +1 -0
- package/dist/page-overrides/ClickOverride.d.ts +6 -0
- package/dist/page-overrides/ClickOverride.js +73 -0
- package/dist/page-overrides/ClickOverride.js.map +1 -0
- package/dist/services/ConfigBuilder.d.ts +12 -0
- package/dist/services/ConfigBuilder.js +30 -0
- package/dist/services/ConfigBuilder.js.map +1 -0
- package/dist/services/Logger.d.ts +8 -0
- package/dist/services/Logger.js +106 -0
- package/dist/services/Logger.js.map +1 -0
- package/dist/services/ReportingService.d.ts +8 -0
- package/dist/services/ReportingService.js +29 -0
- package/dist/services/ReportingService.js.map +1 -0
- package/dist/types/CoreTypes.d.ts +2 -0
- package/dist/types/CoreTypes.js +2 -0
- package/dist/types/CoreTypes.js.map +1 -0
- package/dist/types/universal.d.ts +25 -0
- package/dist/types/universal.js +2 -0
- package/dist/types/universal.js.map +1 -0
- package/enums/ActionbarRegions.ts +5 -0
- package/enums/ButtonEnums.ts +28 -0
- package/enums/ExportFormat.ts +5 -0
- package/enums/LogLevel.ts +9 -0
- package/example-code/TestifyService.ts +201 -0
- package/helpers/InflightRequestTracker.ts +33 -0
- package/helpers/LoginHelper.ts +140 -0
- package/helpers/UserSimulationHelper.ts +72 -0
- package/index.ts +28 -0
- package/package.json +40 -0
- package/page-extensions/GoToDeepLink.ts +29 -0
- package/page-extensions/LoginFeatures.ts +50 -0
- package/page-extensions/UserSimulation.ts +49 -0
- package/page-overrides/ClickOverride.ts +89 -0
- package/promptCredentials.js +113 -0
- package/scripts/Testwise.template.json +19 -0
- package/scripts/add-config.js +23 -0
- package/services/ConfigBuilder.ts +40 -0
- package/services/Logger.ts +125 -0
- package/services/ReportingService.ts +41 -0
- package/types/CoreTypes.ts +9 -0
- package/types/universal.ts +26 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import { GridObjects } from './GridObjects.js';
|
|
3
|
+
|
|
4
|
+
export class Grid {
|
|
5
|
+
private _gridObjects: GridObjects;
|
|
6
|
+
private _page: Page;
|
|
7
|
+
|
|
8
|
+
constructor(page: Page, gridContext: Locator | null = null) {
|
|
9
|
+
this._gridObjects = new GridObjects(page, gridContext);
|
|
10
|
+
this._page = page;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Retrieves a column header element by its text content.
|
|
15
|
+
* @param text
|
|
16
|
+
* @returns
|
|
17
|
+
*/
|
|
18
|
+
public getColumnHeaderByText = (text: string): Locator => this._gridObjects.columnHeaderByText(text);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves a row element by its index.
|
|
22
|
+
* @param rowIndex - The index of the row to retrieve.
|
|
23
|
+
* @returns A Locator for the specified row.
|
|
24
|
+
*/
|
|
25
|
+
public getRowByIndex = (rowIndex: number): Locator => this._gridObjects.rowByIndex(rowIndex);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Retrieves a cell element by its exact text value.
|
|
29
|
+
* @param value - The exact text value of the cell to retrieve.
|
|
30
|
+
* @returns A Locator for the cell containing the specified value.
|
|
31
|
+
*/
|
|
32
|
+
public getCellByExactValue = (value: string): Locator => this._gridObjects.cellByValue(value);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Opens the Excel-style filter popup for the specified grid column.
|
|
36
|
+
*
|
|
37
|
+
* This method locates the column header using its accessible name,
|
|
38
|
+
* then clicks the overflow menu icon within it to open the filter popup.
|
|
39
|
+
*
|
|
40
|
+
* @param columnName - The name of the column for which to open the filter popup.
|
|
41
|
+
* @returns A promise that resolves when the filter popup is opened.
|
|
42
|
+
*/
|
|
43
|
+
public openExcelStyleFilterPopup = async (columnName: string) =>
|
|
44
|
+
await this._gridObjects.menuIconByColumnHeaderName(columnName).click();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Retrieves all rows in the grid that match a specific column value.
|
|
48
|
+
* @param colIdToMatchOn
|
|
49
|
+
* @param valueToMatch
|
|
50
|
+
* @returns A promise that resolves to an array of Locators for the matching rows.
|
|
51
|
+
*/
|
|
52
|
+
public async getRowsByColValue(colIdToMatchOn: string, valueToMatch: string): Promise<Locator[]> {
|
|
53
|
+
const allRows = await this._gridObjects.rows().all();
|
|
54
|
+
|
|
55
|
+
const filteredRows = await Promise.all(
|
|
56
|
+
allRows.map(async (row) => {
|
|
57
|
+
const text = await row.locator(`[col-id="${colIdToMatchOn}"]`).textContent();
|
|
58
|
+
return text === valueToMatch ? row : null;
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return filteredRows.filter((row) => row !== null) as Locator[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Scrolls through the vertical grid viewport to ensure that all rows are loaded.
|
|
67
|
+
*
|
|
68
|
+
* This method scrolls the grid's vertical scrollbar in increments equal to the visible height of the viewport
|
|
69
|
+
* until no further scrolling is possible. It optionally executes a callback after each scroll step, which can
|
|
70
|
+
* be used to process loaded content (e.g., extract cell values).
|
|
71
|
+
*
|
|
72
|
+
* @param {Locator} scrollbar - Locator pointing to the grid's vertical scrollbar container.
|
|
73
|
+
* @param {() => Promise<void>} onScrollStep - Optional async callback invoked after each scroll step.
|
|
74
|
+
* @returns {Promise<void>} A promise that resolves once the scrolling is complete.
|
|
75
|
+
*/
|
|
76
|
+
private async scrollGridToLoadAllRows(scrollbar: Locator, onScrollStep?: () => Promise<void>): Promise<void> {
|
|
77
|
+
// Reset scroll to the top to start from the beginning
|
|
78
|
+
await scrollbar.evaluate((el) => {
|
|
79
|
+
el.scrollTop = 0;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let lastScrollTop = -1;
|
|
83
|
+
|
|
84
|
+
while (true) {
|
|
85
|
+
// Get the current vertical scroll position
|
|
86
|
+
const scrollTop = await scrollbar.evaluate((el) => el.scrollTop);
|
|
87
|
+
|
|
88
|
+
// Stop if the scroll position hasn’t changed — end of scroll reached
|
|
89
|
+
if (scrollTop === lastScrollTop) break;
|
|
90
|
+
|
|
91
|
+
lastScrollTop = scrollTop;
|
|
92
|
+
|
|
93
|
+
// Scroll one viewport height down
|
|
94
|
+
await scrollbar.evaluate((el) => {
|
|
95
|
+
el.scrollBy({ top: el.clientHeight, behavior: 'instant' });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Optional callback to collect or act on content loaded during this scroll step
|
|
99
|
+
if (onScrollStep) {
|
|
100
|
+
await onScrollStep();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Wait briefly for the new rows to render
|
|
104
|
+
await this._page.waitForTimeout(100);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Retrieves all unique cell values from a specific column in the grid,
|
|
110
|
+
* including values that are only loaded upon scrolling.
|
|
111
|
+
*
|
|
112
|
+
* Internally uses vertical scrolling to ensure that all rows are loaded,
|
|
113
|
+
* and collects cell text content from the specified column.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} colId - The ID of the column to extract values from.
|
|
116
|
+
* @returns {Promise<string[]>} A promise that resolves to an array of unique values from the column.
|
|
117
|
+
*/
|
|
118
|
+
public async getCellValuesByColId(colId: string): Promise<string[]> {
|
|
119
|
+
// Locator for vertical scrollbar used to scroll through grid rows
|
|
120
|
+
const scrollbar = this._gridObjects.verticalScrollbar();
|
|
121
|
+
// Locator for cells in the specified column using AG Grid col-id
|
|
122
|
+
const cellsLocator = this._gridObjects.cellsByColumnId(colId);
|
|
123
|
+
// Use a Set to ensure uniqueness of values
|
|
124
|
+
const collected = new Set<string>();
|
|
125
|
+
// Scroll the grid and collect visible cell values after each scroll step
|
|
126
|
+
await this.scrollGridToLoadAllRows(scrollbar, async () => {
|
|
127
|
+
const visibleValues = await cellsLocator.allTextContents();
|
|
128
|
+
|
|
129
|
+
for (const value of visibleValues.map((value: string) => value.trim()).filter(Boolean)) {
|
|
130
|
+
collected.add(value);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Return unique values as an array
|
|
134
|
+
return Array.from(collected);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Scrolls through the horizontal grid viewport to ensure all columns are loaded.
|
|
139
|
+
*
|
|
140
|
+
* This method scrolls the grid's horizontal scrollbar in increments equal to the visible width
|
|
141
|
+
* of the viewport until no further scrolling is possible. It optionally executes a callback
|
|
142
|
+
* after each scroll step, useful for collecting or processing visible column headers.
|
|
143
|
+
*
|
|
144
|
+
* @param {Locator} scrollbar - Locator pointing to the horizontal scrollbar container.
|
|
145
|
+
* @param {() => Promise<void>} onScrollStep - Optional async callback executed after each scroll step.
|
|
146
|
+
* @returns {Promise<void>} A promise that resolves when horizontal scrolling is complete.
|
|
147
|
+
*/
|
|
148
|
+
private async scrollGridHorizontallyToLoadAllColumns(
|
|
149
|
+
scrollbar: Locator,
|
|
150
|
+
onScrollStep?: () => Promise<void>
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
// Reset scroll to the far left
|
|
153
|
+
await scrollbar.evaluate((el) => {
|
|
154
|
+
el.scrollLeft = 0;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
let lastScrollLeft = -1;
|
|
158
|
+
|
|
159
|
+
while (true) {
|
|
160
|
+
const scrollLeft = await scrollbar.evaluate((el) => el.scrollLeft);
|
|
161
|
+
|
|
162
|
+
// Exit loop if no further horizontal scroll is possible
|
|
163
|
+
if (scrollLeft === lastScrollLeft) break;
|
|
164
|
+
lastScrollLeft = scrollLeft;
|
|
165
|
+
|
|
166
|
+
// Scroll one viewport width to the right
|
|
167
|
+
await scrollbar.evaluate((el) => {
|
|
168
|
+
el.scrollBy({ left: el.clientWidth, behavior: 'instant' });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (onScrollStep) {
|
|
172
|
+
await onScrollStep();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await this._page.waitForTimeout(100); // Allow time for rendering
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Retrieves all column header values from the grid, including those that require horizontal scrolling to load.
|
|
181
|
+
*
|
|
182
|
+
* This method scrolls through the grid horizontally to ensure all columns are rendered,
|
|
183
|
+
* collects the text content of each header, and returns a unique list of trimmed, non-empty values.
|
|
184
|
+
*
|
|
185
|
+
* @returns {Promise<string[]>} A promise that resolves to an array of unique column header values.
|
|
186
|
+
*/
|
|
187
|
+
public async getColumnHeaderValues(): Promise<string[]> {
|
|
188
|
+
const scrollbar = this._gridObjects.horizontalScrollbar();
|
|
189
|
+
const headersLocator = this._gridObjects.columnHeaders();
|
|
190
|
+
const collected = new Set<string>();
|
|
191
|
+
|
|
192
|
+
await this.scrollGridHorizontallyToLoadAllColumns(scrollbar, async () => {
|
|
193
|
+
const visibleHeaders = await headersLocator.allTextContents();
|
|
194
|
+
for (const header of visibleHeaders.map((header) => header.trim()).filter(Boolean)) {
|
|
195
|
+
collected.add(header);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return Array.from(collected);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Filters a column in an Excel-style grid by selecting a specific value.
|
|
204
|
+
*
|
|
205
|
+
* @param columnName - The name of the column to filter.
|
|
206
|
+
* @param valueToSelect - The value to select in the filter dropdown.
|
|
207
|
+
* @returns A promise that resolves when the filtering operation is complete.
|
|
208
|
+
*/
|
|
209
|
+
public async filterByColumnValueExcelStyle(columnName: string, valueToSelect: string): Promise<void> {
|
|
210
|
+
await this.openExcelStyleFilterPopup(columnName);
|
|
211
|
+
const popup = this._gridObjects.excelStyleFilterPopup();
|
|
212
|
+
await popup.waitFor({ state: 'visible' });
|
|
213
|
+
const checkbox = this._gridObjects.excelStyleFilterPopupOptionByText(valueToSelect);
|
|
214
|
+
await checkbox.check();
|
|
215
|
+
|
|
216
|
+
// Click outside the popup to close it
|
|
217
|
+
await this._gridObjects.gridCell().first().click();
|
|
218
|
+
|
|
219
|
+
await popup.waitFor({ state: 'detached' });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import { BaseComponentObjects } from '../BaseComponentObjects.js';
|
|
3
|
+
|
|
4
|
+
export class GridObjects extends BaseComponentObjects {
|
|
5
|
+
private _page: Page;
|
|
6
|
+
|
|
7
|
+
constructor(page: Page, context: Locator | null = null) {
|
|
8
|
+
super(page, context);
|
|
9
|
+
this._page = page;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public columnHeaderByText = (text: string) => this.context.locator('[role="columnheader"]', { hasText: text });
|
|
13
|
+
|
|
14
|
+
public columnHeaders = () => this.context.locator('[role="columnheader"]');
|
|
15
|
+
|
|
16
|
+
public columnHeaderByName = (columnName: string) => this.context.getByRole('columnheader', { name: columnName });
|
|
17
|
+
|
|
18
|
+
public menuIconByColumnHeaderName = (columnName: string) =>
|
|
19
|
+
this.columnHeaderByName(columnName).getByTestId('grid__custom__menu__icon');
|
|
20
|
+
|
|
21
|
+
public rowByIndex = (row: number) => this.context.locator(`[role="row"][row-index="${row}"]`);
|
|
22
|
+
|
|
23
|
+
public cellByValue = (value: string) =>
|
|
24
|
+
this.context.locator('[role="button"]', { hasText: new RegExp(`^${value}$`) });
|
|
25
|
+
|
|
26
|
+
public rows = () => this.context.locator('[role="row"]');
|
|
27
|
+
|
|
28
|
+
public verticalScrollbar = () => this.context.locator('.ag-body-vertical-scroll-viewport');
|
|
29
|
+
|
|
30
|
+
public horizontalScrollbar = () => this.context.locator('.ag-body-horizontal-scroll-viewport');
|
|
31
|
+
|
|
32
|
+
public cellsByColumnId = (colId: string) => this.context.locator(`[col-id="${colId}"] .value-renderer`);
|
|
33
|
+
|
|
34
|
+
public columnByRowLocator = (rowLocator: Locator) => rowLocator.locator('.ag-cell');
|
|
35
|
+
|
|
36
|
+
public excelStyleFilterPopup = () => this._page.locator('.ag-popup-child[role="dialog"]');
|
|
37
|
+
|
|
38
|
+
public excelStyleFilterPopupOptionByText = (optionText: string) =>
|
|
39
|
+
this.excelStyleFilterPopup()
|
|
40
|
+
.locator('[data-testid$="list-option-name"]', { hasText: optionText })
|
|
41
|
+
.locator('xpath=..')
|
|
42
|
+
.locator('input[type="checkbox"]');
|
|
43
|
+
|
|
44
|
+
public gridCell = () => this.context.locator('[role="gridcell"]');
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
import { PopUpComponentObjects } from './PopUpComponentModels.js';
|
|
3
|
+
|
|
4
|
+
export class PopUpComponent {
|
|
5
|
+
private _objects: PopUpComponentObjects;
|
|
6
|
+
|
|
7
|
+
constructor(page: Page) {
|
|
8
|
+
this._objects = new PopUpComponentObjects(page);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public confirmYes = async () => await this._objects.actionYesButton().click();
|
|
12
|
+
|
|
13
|
+
public confirmNo = async () => await this._objects.actionNoButton().click();
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export class PopUpComponentObjects {
|
|
4
|
+
private _page: Page;
|
|
5
|
+
|
|
6
|
+
constructor(page: Page) {
|
|
7
|
+
this._page = page;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public actionNoButton = () => this._page.getByTestId('popup__translatemessage__actions__no');
|
|
11
|
+
|
|
12
|
+
public actionYesButton = () => this._page.getByTestId('popup__translatemessage__actions__yes');
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
import { BaseComponent } from '../BaseComponent';
|
|
3
|
+
|
|
4
|
+
export class Tab extends BaseComponent {
|
|
5
|
+
public async getLocator(context?: Locator): Promise<Locator> {
|
|
6
|
+
if (context) {
|
|
7
|
+
return context.locator(`[class*="${this.id}"]`);
|
|
8
|
+
}
|
|
9
|
+
return this.page.locator(`[class*="${this.id}"]`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class DetailTabPage extends BaseComponent {
|
|
14
|
+
public async getLocator(context?: Locator): Promise<Locator> {
|
|
15
|
+
if (context) {
|
|
16
|
+
return context.getByTestId(this.id);
|
|
17
|
+
}
|
|
18
|
+
return this.page.getByTestId(this.id);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ComponentTabPage extends BaseComponent {
|
|
23
|
+
public async getLocator(context?: Locator): Promise<Locator> {
|
|
24
|
+
if (context) {
|
|
25
|
+
return context.getByTestId(this.id);
|
|
26
|
+
}
|
|
27
|
+
return this.page.getByTestId(this.id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
import { BaseComponent } from '../BaseComponent';
|
|
3
|
+
|
|
4
|
+
export class TaskBar extends BaseComponent {
|
|
5
|
+
public async getLocator(context?: Locator): Promise<Locator> {
|
|
6
|
+
if (context) {
|
|
7
|
+
return context.getByTestId(this.id);
|
|
8
|
+
}
|
|
9
|
+
return this.page.getByTestId(this.id);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
import { BaseComponent } from '../BaseComponent';
|
|
3
|
+
|
|
4
|
+
export class TaskTiles extends BaseComponent {
|
|
5
|
+
public async getLocator(context?: Locator): Promise<Locator> {
|
|
6
|
+
if (context) {
|
|
7
|
+
return context.getByTestId(this.id);
|
|
8
|
+
}
|
|
9
|
+
return this.page.getByTestId(this.id);
|
|
10
|
+
}
|
|
11
|
+
}
|
package/config.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"defaultApplication": "",
|
|
3
|
+
"defaultPlatform": 3,
|
|
4
|
+
"loginOptionsDisabled": false,
|
|
5
|
+
"loginOptionsHidden": false,
|
|
6
|
+
"installNotificationDisabled": true,
|
|
7
|
+
"installNotificationExpirationInDays": 30,
|
|
8
|
+
"useServiceWorker": false,
|
|
9
|
+
"useFormFieldBackgroundColor": true
|
|
10
|
+
}
|
package/dist/Testwise.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { test as base } from '@playwright/test';
|
|
2
|
+
import { GoToDeepLink } from './page-extensions/GoToDeepLink.js';
|
|
3
|
+
import { LoginFeatures } from './page-extensions/LoginFeatures.js';
|
|
4
|
+
import { UserSimulation } from './page-extensions/UserSimulation.js';
|
|
5
|
+
import { ClickOverride } from './page-overrides/ClickOverride.js';
|
|
6
|
+
function combineExtensions(baseTest, ...extensions) {
|
|
7
|
+
return extensions.reduce((test, Extension) => new Extension(test).test, baseTest);
|
|
8
|
+
}
|
|
9
|
+
export const test = combineExtensions(base,
|
|
10
|
+
// Override section
|
|
11
|
+
ClickOverride,
|
|
12
|
+
// Extend section
|
|
13
|
+
GoToDeepLink, LoginFeatures, UserSimulation);
|
|
14
|
+
//# sourceMappingURL=Testwise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Testwise.js","sourceRoot":"","sources":["../Testwise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGlE,SAAS,iBAAiB,CAAC,QAAc,EAAE,GAAG,UAAkD;IAC9F,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAS,iBAAiB,CACzC,IAAI;AAEJ,mBAAmB;AACnB,aAAa;AAEb,iBAAiB;AACjB,YAAY,EACZ,aAAa,EACb,cAAc,CACf,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import type { TSFComponent } from './TSFComponent.js';
|
|
3
|
+
export declare abstract class BaseComponent implements TSFComponent {
|
|
4
|
+
protected page: Page;
|
|
5
|
+
protected parentComponent: BaseComponent;
|
|
6
|
+
id: string;
|
|
7
|
+
constructor(page: Page, testid: string, parentComponent?: BaseComponent);
|
|
8
|
+
protected addParentComponent(parentComponent: BaseComponent): void;
|
|
9
|
+
/**
|
|
10
|
+
* Abstract method to retrieve the locator for the component. Implementations should define how to locate the component
|
|
11
|
+
* within the given context or the page.
|
|
12
|
+
*
|
|
13
|
+
* @param {Locator} [context] - Optional context within which to locate the component.
|
|
14
|
+
* @returns {Promise<Locator>} A promise that resolves to the component's locator.
|
|
15
|
+
*/
|
|
16
|
+
abstract getLocator(context?: Locator): Promise<Locator>;
|
|
17
|
+
/**
|
|
18
|
+
* Retrieves the component's locator. If the component's context locator exists, it waits for it to be available
|
|
19
|
+
* and returns it. Otherwise, it falls back to retrieving the locator without context.
|
|
20
|
+
*
|
|
21
|
+
* @returns {Promise<Locator>} A promise that resolves to the component's locator.
|
|
22
|
+
*/
|
|
23
|
+
getComponent(): Promise<Locator>;
|
|
24
|
+
/**
|
|
25
|
+
* Helper to wait for a locator with retries.
|
|
26
|
+
*/
|
|
27
|
+
private waitForWithRetry;
|
|
28
|
+
private getComponentContext;
|
|
29
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export class BaseComponent {
|
|
2
|
+
constructor(page, testid, parentComponent) {
|
|
3
|
+
this.page = page;
|
|
4
|
+
this.id = testid;
|
|
5
|
+
if (parentComponent) {
|
|
6
|
+
this.addParentComponent(parentComponent);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
addParentComponent(parentComponent) {
|
|
10
|
+
this.parentComponent = parentComponent;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves the component's locator. If the component's context locator exists, it waits for it to be available
|
|
14
|
+
* and returns it. Otherwise, it falls back to retrieving the locator without context.
|
|
15
|
+
*
|
|
16
|
+
* @returns {Promise<Locator>} A promise that resolves to the component's locator.
|
|
17
|
+
*/
|
|
18
|
+
async getComponent() {
|
|
19
|
+
const timeout = 250; // per attempt
|
|
20
|
+
const retries = 3;
|
|
21
|
+
const delayMs = 500;
|
|
22
|
+
// Try to get the component locator with existing context
|
|
23
|
+
const componentLocator = await this.getComponentContext();
|
|
24
|
+
if (componentLocator) {
|
|
25
|
+
// If the component locator can be found on the page, return it
|
|
26
|
+
const found = await this.waitForWithRetry(componentLocator, timeout, retries, delayMs);
|
|
27
|
+
if (found) {
|
|
28
|
+
return componentLocator;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// If no context locator is found, return the component locator without context
|
|
32
|
+
return await this.getLocator();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Helper to wait for a locator with retries.
|
|
36
|
+
*/
|
|
37
|
+
async waitForWithRetry(locator, timeout, retries, delayMs) {
|
|
38
|
+
for (let attempt = 0; attempt < retries; attempt++) {
|
|
39
|
+
try {
|
|
40
|
+
await locator.waitFor({ timeout });
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
if (attempt < retries - 1) {
|
|
45
|
+
await new Promise((res) => setTimeout(res, delayMs));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
async getComponentContext() {
|
|
52
|
+
// If there is no parent component, check if the current locator exists and return it
|
|
53
|
+
if (!this.parentComponent) {
|
|
54
|
+
const currentLocator = await this.getLocator();
|
|
55
|
+
return currentLocator;
|
|
56
|
+
}
|
|
57
|
+
// Recursively call the method on the parent component
|
|
58
|
+
const parentLocator = await this.parentComponent.getComponentContext();
|
|
59
|
+
if (parentLocator) {
|
|
60
|
+
const currentLocator = await this.getLocator(parentLocator);
|
|
61
|
+
return currentLocator;
|
|
62
|
+
}
|
|
63
|
+
// If neither the parent nor the current locator exists, return undefined
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=BaseComponent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseComponent.js","sourceRoot":"","sources":["../../components/BaseComponent.ts"],"names":[],"mappings":"AAGA,MAAM,OAAgB,aAAa;IAMjC,YAAY,IAAU,EAAE,MAAc,EAAE,eAA+B;QACrE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC;QAEjB,IAAI,eAAe,EAAE,CAAC;YACpB,IAAI,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAES,kBAAkB,CAAC,eAA8B;QACzD,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;IAWD;;;;;OAKG;IACI,KAAK,CAAC,YAAY;QACvB,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,cAAc;QACnC,MAAM,OAAO,GAAG,CAAC,CAAC;QAClB,MAAM,OAAO,GAAG,GAAG,CAAC;QACpB,yDAAyD;QACzD,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAE1D,IAAI,gBAAgB,EAAE,CAAC;YACrB,+DAA+D;YAC/D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YACvF,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,gBAAgB,CAAC;YAC1B,CAAC;QACH,CAAC;QAED,+EAA+E;QAC/E,OAAO,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;IACjC,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,gBAAgB,CAC5B,OAAgB,EAChB,OAAe,EACf,OAAe,EACf,OAAe;QAEf,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC;YACnD,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;gBACnC,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,OAAO,GAAG,OAAO,GAAG,CAAC,EAAE,CAAC;oBAC1B,MAAM,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;gBACvD,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,KAAK,CAAC,mBAAmB;QAC/B,qFAAqF;QACrF,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YAC/C,OAAO,cAAc,CAAC;QACxB,CAAC;QAED,sDAAsD;QACtD,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,mBAAmB,EAAE,CAAC;QACvE,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YAC5D,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,yEAAyE;QACzE,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import type { IComponentObjects } from './IComponentObjects.js';
|
|
3
|
+
export declare abstract class BaseComponentObjects implements IComponentObjects {
|
|
4
|
+
context: Locator;
|
|
5
|
+
constructor(page: Page, context?: Locator | null);
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseComponentObjects.js","sourceRoot":"","sources":["../../components/BaseComponentObjects.ts"],"names":[],"mappings":"AAGA,MAAM,OAAgB,oBAAoB;IAGxC,YAAY,IAAU,EAAE,UAA0B,IAAI;QACpD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1D,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IComponentObjects.js","sourceRoot":"","sources":["../../components/IComponentObjects.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseComponent } from './BaseComponent.js';
|
|
2
|
+
export class Splitter extends BaseComponent {
|
|
3
|
+
async getLocator(context) {
|
|
4
|
+
if (context) {
|
|
5
|
+
return context.locator(`[data-type="SplitPane"][class*="${this.id}"]`);
|
|
6
|
+
}
|
|
7
|
+
return this.page.locator(`[data-type="SplitPane"][class*="${this.id}"]`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=Splitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Splitter.js","sourceRoot":"","sources":["../../components/Splitter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,OAAO,QAAS,SAAQ,aAAa;IAClC,KAAK,CAAC,UAAU,CAAC,OAAiB;QACvC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,OAAO,CAAC,OAAO,CAAC,mCAAmC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,mCAAmC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAC3E,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TSFComponent.js","sourceRoot":"","sources":["../../components/TSFComponent.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
export declare class Actionbar {
|
|
3
|
+
private _objects;
|
|
4
|
+
constructor(page: Page, actionbarContext?: Locator | null);
|
|
5
|
+
/**
|
|
6
|
+
* Opens the Actionbar’s overflow menu and clicks on the 'Export' button inside it.
|
|
7
|
+
*/
|
|
8
|
+
clickExportButton(): Promise<void>;
|
|
9
|
+
getAddButton: () => Locator;
|
|
10
|
+
getCancelButton: () => Locator;
|
|
11
|
+
getCopyButton: () => Locator;
|
|
12
|
+
getDeleteButton: () => Locator;
|
|
13
|
+
getRefreshButton: () => Locator;
|
|
14
|
+
getSaveButton: () => Locator;
|
|
15
|
+
getUpdateButton: () => Locator;
|
|
16
|
+
getOverflowMenuButton: () => Locator;
|
|
17
|
+
getExportButton: () => Locator;
|
|
18
|
+
getImportButton: () => Locator;
|
|
19
|
+
getExportImmediatelyButton: () => Locator;
|
|
20
|
+
getMassUpdateButton: () => Locator;
|
|
21
|
+
getQuickFilterButton: () => Locator;
|
|
22
|
+
getFilterButton: () => Locator;
|
|
23
|
+
getClearAllFiltersButton: () => Locator;
|
|
24
|
+
getRestoreSortOrderButton: () => Locator;
|
|
25
|
+
getSortButton: () => Locator;
|
|
26
|
+
getSearchInput: () => Locator;
|
|
27
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ActionbarObjects } from './ActionbarObjects.js';
|
|
2
|
+
export class Actionbar {
|
|
3
|
+
constructor(page, actionbarContext = null) {
|
|
4
|
+
// CRUD Buttons
|
|
5
|
+
this.getAddButton = () => this._objects.addButton();
|
|
6
|
+
this.getCancelButton = () => this._objects.cancelButton();
|
|
7
|
+
this.getCopyButton = () => this._objects.copyButton();
|
|
8
|
+
this.getDeleteButton = () => this._objects.deleteButton();
|
|
9
|
+
this.getRefreshButton = () => this._objects.refreshButton();
|
|
10
|
+
this.getSaveButton = () => this._objects.saveButton();
|
|
11
|
+
this.getUpdateButton = () => this._objects.updateButton();
|
|
12
|
+
// Overflow Menu
|
|
13
|
+
this.getOverflowMenuButton = () => this._objects.overflowMenuButton();
|
|
14
|
+
this.getExportButton = () => this._objects.exportButton();
|
|
15
|
+
this.getImportButton = () => this._objects.importButton();
|
|
16
|
+
this.getExportImmediatelyButton = () => this._objects.exportImmediatelyButton();
|
|
17
|
+
this.getMassUpdateButton = () => this._objects.massUpdateButton();
|
|
18
|
+
this.getQuickFilterButton = () => this._objects.quickFilterButton();
|
|
19
|
+
this.getFilterButton = () => this._objects.filterButton();
|
|
20
|
+
this.getClearAllFiltersButton = () => this._objects.clearAllFiltersButton();
|
|
21
|
+
this.getRestoreSortOrderButton = () => this._objects.restoreSortOrderButton();
|
|
22
|
+
this.getSortButton = () => this._objects.sortButton();
|
|
23
|
+
// Other Elements
|
|
24
|
+
this.getSearchInput = () => this._objects.searchInput();
|
|
25
|
+
this._objects = new ActionbarObjects(page, actionbarContext);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Opens the Actionbar’s overflow menu and clicks on the 'Export' button inside it.
|
|
29
|
+
*/
|
|
30
|
+
async clickExportButton() {
|
|
31
|
+
await this._objects.overflowMenuButton().click();
|
|
32
|
+
await this._objects.exportButton().click();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=Actionbar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Actionbar.js","sourceRoot":"","sources":["../../../components/actionbar/Actionbar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,OAAO,SAAS;IAGpB,YAAY,IAAU,EAAE,mBAAmC,IAAI;QAY/D,eAAe;QACR,iBAAY,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QAExD,oBAAe,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;QAE9D,kBAAa,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QAE1D,oBAAe,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;QAE9D,qBAAgB,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QAEhE,kBAAa,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QAE1D,oBAAe,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;QAErE,gBAAgB;QACT,0BAAqB,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC;QAE1E,oBAAe,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;QAE9D,oBAAe,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;QAE9D,+BAA0B,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,uBAAuB,EAAE,CAAC;QAEpF,wBAAmB,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,EAAE,CAAC;QAEtE,yBAAoB,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QAExE,oBAAe,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;QAE9D,6BAAwB,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,qBAAqB,EAAE,CAAC;QAEhF,8BAAyB,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,sBAAsB,EAAE,CAAC;QAElF,kBAAa,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QAEjE,iBAAiB;QACV,mBAAc,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAhDjE,IAAI,CAAC,QAAQ,GAAG,IAAI,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC/D,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,iBAAiB;QAC5B,MAAM,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,CAAC;QACjD,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,CAAC;IAC7C,CAAC;CAwCF"}
|