@sap-ux/ui5-test-writer 0.7.51 → 0.7.53

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/README.md CHANGED
@@ -19,9 +19,26 @@ Pnpm
19
19
 
20
20
  2 public methods are available: one to generate all OPA test files for a Fiori elements for OData V4 application, another one to generate an additional page object file for a Fiori elements for OData V4 application.
21
21
 
22
-
23
22
  ### Generate all OPA test files for a Fiori elements for OData V4 application
24
23
 
24
+ The `generateOPAFiles` function creates OPA5 tests suite for Fiori elements applications by analyzing the project structure and generating dynamic test cases based on actual application configuration.
25
+
26
+ #### How it works:
27
+
28
+ 1. **Analyzes the project** using `@sap/ux-specification` to create an application model that represents the UI structure
29
+ 2. **Extracts UI controls** from the applications page models, for example:
30
+ - Filter bar fields from the FilterBar control
31
+ - Table columns from the Table control aggregations
32
+ 3. **Generates test files** based on the extracted information:
33
+ - Common test infrastructure files (e.g. testsuite)
34
+ - Page object files for each page defined in the routing targets
35
+ - Journey files with dynamic test assertions for application configuration
36
+ - Journey runner configuration
37
+
38
+ This approach ensures that OPA tests are tailored to the specific features and controls present in your application, rather than generating generic tests that might not match your actual UI.
39
+
40
+ #### Usage Example
41
+
25
42
  Calling the `generateOPAFiles` function
26
43
  ```javascript
27
44
  import { generateOPAFiles } from '@sap-ux/ui5-test-writer'
@@ -1,5 +1,6 @@
1
1
  import type { Editor } from 'mem-fs-editor';
2
2
  import type { Manifest } from '@sap-ux/project-access';
3
+ import type { Logger } from '@sap-ux/logger';
3
4
  /**
4
5
  * Reads the manifest for an app.
5
6
  *
@@ -18,13 +19,14 @@ export declare function readManifest(fs: Editor, basePath: string): Manifest;
18
19
  * @param opaConfig.htmlTarget - the name of the html that will be used in OPA journey file. If not specified, 'index.html' will be used
19
20
  * @param opaConfig.appID - the appID. If not specified, will be read from the manifest in sap.app/id
20
21
  * @param fs - an optional reference to a mem-fs editor
22
+ * @param log - optional logger instance
21
23
  * @returns Reference to a mem-fs-editor
22
24
  */
23
25
  export declare function generateOPAFiles(basePath: string, opaConfig: {
24
26
  scriptName?: string;
25
27
  appID?: string;
26
28
  htmlTarget?: string;
27
- }, fs?: Editor): Editor;
29
+ }, fs?: Editor, log?: Logger): Promise<Editor>;
28
30
  /**
29
31
  * Generate a page object file for a Fiori elements for OData V4 application.
30
32
  * Note: this doesn't modify other existing files in the webapp/test folder.
@@ -9,6 +9,7 @@ const mem_fs_editor_1 = require("mem-fs-editor");
9
9
  const types_1 = require("./types");
10
10
  const i18n_1 = require("./i18n");
11
11
  const project_access_1 = require("@sap-ux/project-access");
12
+ const modelUtils_1 = require("./utils/modelUtils");
12
13
  /**
13
14
  * Reads the manifest for an app.
14
15
  *
@@ -214,9 +215,10 @@ function writePageObject(pageConfig, rootTemplateDirPath, testOutDirPath, fs) {
214
215
  * @param opaConfig.htmlTarget - the name of the html that will be used in OPA journey file. If not specified, 'index.html' will be used
215
216
  * @param opaConfig.appID - the appID. If not specified, will be read from the manifest in sap.app/id
216
217
  * @param fs - an optional reference to a mem-fs editor
218
+ * @param log - optional logger instance
217
219
  * @returns Reference to a mem-fs-editor
218
220
  */
219
- function generateOPAFiles(basePath, opaConfig, fs) {
221
+ async function generateOPAFiles(basePath, opaConfig, fs, log) {
220
222
  const editor = fs ?? (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
221
223
  const manifest = readManifest(editor, basePath);
222
224
  const { applicationType, hideFilterBar } = getAppTypeAndHideFilterBarFromManifest(manifest);
@@ -241,11 +243,15 @@ function generateOPAFiles(basePath, opaConfig, fs) {
241
243
  // OPA Journey file
242
244
  const startPages = config.pages.filter((page) => page.isStartup).map((page) => page.targetKey);
243
245
  const LROP = findLROP(config.pages, manifest);
246
+ // Access ux-specification to get feature data for OPA test generation
247
+ const { filterBarItems, tableColumns } = await (0, modelUtils_1.getFeatureData)(basePath, editor, log);
244
248
  const journeyParams = {
245
249
  startPages,
246
250
  startLR: LROP.pageLR?.targetKey,
247
251
  navigatedOP: LROP.pageOP?.targetKey,
248
- hideFilterBar: config.hideFilterBar
252
+ hideFilterBar: config.hideFilterBar,
253
+ filterBarItems: filterBarItems,
254
+ tableColumns: tableColumns
249
255
  };
250
256
  editor.copyTpl((0, node_path_1.join)(rootV4TemplateDirPath, 'integration/FirstJourney.js'), (0, node_path_1.join)(testOutDirPath, `integration/${config.opaJourneyFileName}.js`), journeyParams, undefined, {
251
257
  globOptions: { dot: true }
package/dist/types.d.ts CHANGED
@@ -18,6 +18,7 @@ export type FEV4OPAConfig = {
18
18
  opaJourneyFileName: string;
19
19
  htmlTarget: string;
20
20
  hideFilterBar: boolean;
21
+ filterBarItems?: string[];
21
22
  };
22
23
  export type FEV4ManifestTarget = {
23
24
  type?: string;
@@ -61,4 +62,8 @@ export interface FFOPAConfig {
61
62
  ui5Version?: string;
62
63
  ui5Theme?: string;
63
64
  }
65
+ export type FeatureData = {
66
+ filterBarItems?: string[];
67
+ tableColumns?: Record<string, Record<string, string | number | boolean>>;
68
+ };
64
69
  //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,59 @@
1
+ import type { Editor } from 'mem-fs-editor';
2
+ import type { Logger } from '@sap-ux/logger';
3
+ import type { TreeAggregation, TreeAggregations, TreeModel, ApplicationModel } from '@sap/ux-specification/dist/types/src/parser';
4
+ import type { FeatureData } from '../types';
5
+ export interface AggregationItem extends TreeAggregation {
6
+ description: string;
7
+ }
8
+ /**
9
+ * Gets feature data from the application model using ux-specification.
10
+ *
11
+ * @param basePath - the absolute target path where the application will be generated
12
+ * @param fs - optional mem-fs editor instance
13
+ * @param log - optional logger instance
14
+ * @returns feature data extracted from the application model
15
+ */
16
+ export declare function getFeatureData(basePath: string, fs?: Editor, log?: Logger): Promise<FeatureData>;
17
+ /**
18
+ * Retrieves all List Report definitions from the given application model.
19
+ *
20
+ * @param applicationModel - The application model containing page definitions.
21
+ * @returns An array of List Report definitions.
22
+ */
23
+ export declare function getListReportPage<T = ApplicationModel['pages'][string]>(applicationModel: ApplicationModel): T | null;
24
+ /**
25
+ * Retrieves all Object Page definitions from the given application model.
26
+ *
27
+ * @param applicationModel - The application model containing page definitions.
28
+ * @returns An array of Object Page definitions.
29
+ */
30
+ export declare function getObjectPages<T = ApplicationModel['pages'][string]>(applicationModel: ApplicationModel): T[];
31
+ /**
32
+ * Retrieves the aggregations from the given tree aggregations node.
33
+ *
34
+ * @param node - The tree aggregations node.
35
+ * @returns The aggregations object.
36
+ */
37
+ export declare function getAggregations(node: TreeAggregation): TreeAggregations;
38
+ /**
39
+ * Retrieves selection field items from the given selection fields aggregation.
40
+ *
41
+ * @param selectionFieldsAgg - The selection fields aggregation containing field definitions.
42
+ * @returns An array of selection field descriptions.
43
+ */
44
+ export declare function getSelectionFieldItems(selectionFieldsAgg: TreeAggregations): string[];
45
+ /**
46
+ * Retrieves filter field descriptions from the given tree model.
47
+ *
48
+ * @param pageModel - The tree model containing filter bar definitions.
49
+ * @returns An array of filter field descriptions.
50
+ */
51
+ export declare function getFilterFields(pageModel: TreeModel): TreeAggregations;
52
+ /**
53
+ * Retrieves the table columns aggregation from the given tree model.
54
+ *
55
+ * @param pageModel - The tree model containing table column definitions.
56
+ * @returns The table columns aggregation object.
57
+ */
58
+ export declare function getTableColumns(pageModel: TreeModel): TreeAggregations;
59
+ //# sourceMappingURL=modelUtils.d.ts.map
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFeatureData = getFeatureData;
4
+ exports.getListReportPage = getListReportPage;
5
+ exports.getObjectPages = getObjectPages;
6
+ exports.getAggregations = getAggregations;
7
+ exports.getSelectionFieldItems = getSelectionFieldItems;
8
+ exports.getFilterFields = getFilterFields;
9
+ exports.getTableColumns = getTableColumns;
10
+ const project_access_1 = require("@sap-ux/project-access");
11
+ /**
12
+ * Gets feature data from the application model using ux-specification.
13
+ *
14
+ * @param basePath - the absolute target path where the application will be generated
15
+ * @param fs - optional mem-fs editor instance
16
+ * @param log - optional logger instance
17
+ * @returns feature data extracted from the application model
18
+ */
19
+ async function getFeatureData(basePath, fs, log) {
20
+ const featureData = {};
21
+ let listReportPage = null;
22
+ // Read application model to extract control information needed for test generation
23
+ // specification and readApp might not be available due to specification version, fail gracefully
24
+ try {
25
+ // readApp calls createApplicationAccess internally if given a path, but it uses the "live" version of project-access without fs enhancement
26
+ const appAccess = await (0, project_access_1.createApplicationAccess)(basePath, { fs: fs });
27
+ const specification = await appAccess.getSpecification();
28
+ const appResult = await specification.readApp({ app: appAccess, fs: fs });
29
+ listReportPage = appResult.applicationModel ? getListReportPage(appResult.applicationModel) : listReportPage;
30
+ }
31
+ catch (error) {
32
+ log?.warn('Error analyzing project model using specification. No dynamic tests will be generated. Error: ' +
33
+ error.message);
34
+ return featureData;
35
+ }
36
+ if (!listReportPage) {
37
+ log?.warn('List Report page not found in application model. Dynamic tests will not be generated.');
38
+ return featureData;
39
+ }
40
+ // attempt to get individual feature data
41
+ featureData.filterBarItems = getFilterFieldNames(listReportPage.model, log);
42
+ featureData.tableColumns = getTableColumnData(listReportPage.model, log);
43
+ return featureData;
44
+ }
45
+ /**
46
+ * Gets identifier of a column for OPA5 tests.
47
+ * If the column is custom, the identifier is taken from the 'Key' entry in the schema keys.
48
+ * If the column is not custom, the identifier is taken from the 'Value' entry in the schema keys.
49
+ * If no such entry is found, undefined is returned.
50
+ *
51
+ * @param column - column module from ux specification
52
+ * @param column.custom boolean indicating whether the column is custom
53
+ * @param column.schema schema of the column
54
+ * @param column.schema.keys keys of the column; expected to have an entry with the name 'Key' or 'Value'
55
+ * @returns identifier of the column for OPA5 tests; can be the name or index
56
+ */
57
+ function getColumnIdentifier(column) {
58
+ const key = column.custom ? 'Key' : 'Value';
59
+ const keyEntry = column.schema.keys.find((entry) => entry.name === key);
60
+ return keyEntry?.value;
61
+ }
62
+ /**
63
+ * Transforms column aggregations from the ux specification model into a map of columns for OPA5 tests.
64
+ *
65
+ * @param columnAggregations column aggregations from the ux specification model
66
+ * @returns a map of columns for OPA5 tests
67
+ */
68
+ function transformTableColumns(columnAggregations) {
69
+ const columns = {};
70
+ Object.values(columnAggregations).forEach((columnAggregation, index) => {
71
+ columns[getColumnIdentifier(columnAggregation) ?? index] = {
72
+ header: columnAggregation.description
73
+ // TODO possibly more reliable properties could be used?
74
+ };
75
+ });
76
+ return columns;
77
+ }
78
+ /**
79
+ * Retrieves filter field names from the page model using ux-specification.
80
+ *
81
+ * @param pageModel - the tree model containing filter bar definitions
82
+ * @param log - optional logger instance
83
+ * @returns - an array of filter field names
84
+ */
85
+ function getFilterFieldNames(pageModel, log) {
86
+ let filterBarItems = [];
87
+ try {
88
+ const filterBarAggregations = getFilterFields(pageModel);
89
+ filterBarItems = getSelectionFieldItems(filterBarAggregations);
90
+ }
91
+ catch (error) {
92
+ log?.debug(error);
93
+ }
94
+ if (!filterBarItems?.length) {
95
+ log?.warn('Unable to extract filter fields from project model using specification. No filter field tests will be generated.');
96
+ }
97
+ return filterBarItems;
98
+ }
99
+ /**
100
+ * Retrieves table column data from the page model using ux-specification.
101
+ *
102
+ * @param pageModel - the tree model containing table column definitions
103
+ * @param log - optional logger instance
104
+ * @returns - a map of table columns
105
+ */
106
+ function getTableColumnData(pageModel, log) {
107
+ let tableColumns = {};
108
+ try {
109
+ const columnAggregations = getTableColumns(pageModel);
110
+ tableColumns = transformTableColumns(columnAggregations);
111
+ }
112
+ catch (error) {
113
+ log?.debug(error);
114
+ }
115
+ if (!tableColumns || !Object.keys(tableColumns).length) {
116
+ log?.warn('Unable to extract table columns from project model using specification. No table column tests will be generated.');
117
+ }
118
+ return tableColumns;
119
+ }
120
+ /**
121
+ * Retrieves all List Report definitions from the given application model.
122
+ *
123
+ * @param applicationModel - The application model containing page definitions.
124
+ * @returns An array of List Report definitions.
125
+ */
126
+ function getListReportPage(applicationModel) {
127
+ for (const pageKey in applicationModel.pages) {
128
+ const page = applicationModel.pages[pageKey];
129
+ if (page.pageType === 'ListReport') {
130
+ return page;
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ /**
136
+ * Retrieves all Object Page definitions from the given application model.
137
+ *
138
+ * @param applicationModel - The application model containing page definitions.
139
+ * @returns An array of Object Page definitions.
140
+ */
141
+ function getObjectPages(applicationModel) {
142
+ const objectPages = [];
143
+ for (const pageKey in applicationModel.pages) {
144
+ const page = applicationModel.pages[pageKey];
145
+ if (page.pageType === 'ObjectPage') {
146
+ objectPages.push(page);
147
+ }
148
+ }
149
+ return objectPages;
150
+ }
151
+ /**
152
+ * Retrieves the aggregations from the given tree aggregations node.
153
+ *
154
+ * @param node - The tree aggregations node.
155
+ * @returns The aggregations object.
156
+ */
157
+ function getAggregations(node) {
158
+ if (node && typeof node === 'object' && 'aggregations' in node) {
159
+ return node.aggregations;
160
+ }
161
+ return {};
162
+ }
163
+ /**
164
+ * Retrieves selection field items from the given selection fields aggregation.
165
+ *
166
+ * @param selectionFieldsAgg - The selection fields aggregation containing field definitions.
167
+ * @returns An array of selection field descriptions.
168
+ */
169
+ function getSelectionFieldItems(selectionFieldsAgg) {
170
+ if (selectionFieldsAgg && typeof selectionFieldsAgg === 'object') {
171
+ const items = [];
172
+ for (const itemKey in selectionFieldsAgg) {
173
+ items.push(selectionFieldsAgg[itemKey].description);
174
+ }
175
+ return items;
176
+ }
177
+ return [];
178
+ }
179
+ /**
180
+ * Retrieves filter field descriptions from the given tree model.
181
+ *
182
+ * @param pageModel - The tree model containing filter bar definitions.
183
+ * @returns An array of filter field descriptions.
184
+ */
185
+ function getFilterFields(pageModel) {
186
+ const filterBar = getAggregations(pageModel.root)['filterBar'];
187
+ const filterBarAggregations = getAggregations(filterBar);
188
+ const selectionFields = filterBarAggregations['selectionFields'];
189
+ const selectionFieldsAggregations = getAggregations(selectionFields);
190
+ return selectionFieldsAggregations;
191
+ }
192
+ /**
193
+ * Retrieves the table columns aggregation from the given tree model.
194
+ *
195
+ * @param pageModel - The tree model containing table column definitions.
196
+ * @returns The table columns aggregation object.
197
+ */
198
+ function getTableColumns(pageModel) {
199
+ const table = getAggregations(pageModel.root)['table'];
200
+ const tableAggregations = getAggregations(table);
201
+ const columns = tableAggregations['columns'];
202
+ const columnAggregations = getAggregations(columns);
203
+ return columnAggregations;
204
+ }
205
+ //# sourceMappingURL=modelUtils.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sap-ux/ui5-test-writer",
3
3
  "description": "SAP UI5 tests writer",
4
- "version": "0.7.51",
4
+ "version": "0.7.53",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/SAP/open-ux-tools.git",
@@ -24,9 +24,10 @@
24
24
  "i18next": "25.3.0",
25
25
  "mem-fs": "2.1.0",
26
26
  "mem-fs-editor": "9.4.0",
27
+ "@sap/ux-specification": "1.139.2",
28
+ "@sap-ux/logger": "0.8.0",
27
29
  "@sap-ux/ui5-application-writer": "1.7.1",
28
- "@sap-ux/project-access": "1.33.2",
29
- "@sap-ux/logger": "0.8.0"
30
+ "@sap-ux/project-access": "1.34.1"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@types/ejs": "3.1.2",
@@ -9,9 +9,17 @@ sap.ui.define([
9
9
 
10
10
  opaTest("Start application", function (Given, When, Then) {
11
11
  Given.iStartMyApp();
12
- <% startPages.forEach(function(pageName) { %>
13
- Then.onThe<%- pageName%>.iSeeThisPage();
14
- <% });%>
12
+ <%_ startPages.forEach(function(pageName) { %>
13
+ Then.onThe<%- pageName %>.iSeeThisPage();
14
+ <%_ if (filterBarItems && filterBarItems.length > 0) { -%>
15
+ <%_ filterBarItems.forEach(function(item) { _%>
16
+ Then.onThe<%- pageName%>.onFilterBar().iCheckFilterField("<%- item %>");
17
+ <%_ }); -%>
18
+ <%_ } -%>
19
+ <%_ if (tableColumns && Object.keys(tableColumns).length > 0) { _%>
20
+ Then.onThe<%- pageName %>.onTable().iCheckColumns(<%- Object.keys(tableColumns).length %>, <%- JSON.stringify(tableColumns) %>);
21
+ <%_ } %>
22
+ <%_ }); -%>
15
23
  });
16
24
 
17
25
  <% if (startLR) { %>