@thinkwise/testwise 0.2.0-beta.3 → 0.2.4

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.
Files changed (136) hide show
  1. package/Testwise.ts +8 -7
  2. package/artifact-builder/ArtifactManager.ts +0 -1
  3. package/artifact-builder/InterfaceGenerator.ts +73 -83
  4. package/artifact-builder/ModelDataBuilder.ts +74 -86
  5. package/artifact-builder/ModelDataRefiner.ts +18 -13
  6. package/artifact-builder/SchemaGenerator.ts +1 -4
  7. package/artifact-builder/ScreenInterfaceRefiner.ts +0 -5
  8. package/artifact-builder/SelectorBuilder.ts +8 -1
  9. package/artifact-builder/SubjectComponentGenerator.ts +77 -7
  10. package/artifact-builder/SubjectGenerator.ts +3 -3
  11. package/artifact-builder/SubjectRegistration.ts +68 -60
  12. package/artifact-builder/helpers/DataRetriever.ts +6 -3
  13. package/artifact-builder/helpers/NamingHandler.ts +28 -20
  14. package/artifact-builder/helpers/Stopwatch.ts +13 -0
  15. package/artifact-builder/helpers/index.ts +1 -0
  16. package/components/BaseComponentObjects.ts +4 -1
  17. package/components/action-bar/ActionBar.ts +3 -3
  18. package/components/grid/Grid.ts +18 -21
  19. package/components/tab/BaseTab.ts +2 -2
  20. package/components/tab/BaseTabObjects.ts +1 -1
  21. package/components/tab/DetailTabPage.ts +2 -2
  22. package/components/tab/Tab.ts +4 -4
  23. package/controls/LookupDropdown.ts +7 -2
  24. package/dist/Testwise.d.ts +1 -0
  25. package/dist/Testwise.js +15 -6
  26. package/dist/Testwise.js.map +1 -1
  27. package/dist/artifact-builder/ArtifactManager.js.map +1 -1
  28. package/dist/artifact-builder/InterfaceGenerator.d.ts +1 -1
  29. package/dist/artifact-builder/InterfaceGenerator.js +60 -67
  30. package/dist/artifact-builder/InterfaceGenerator.js.map +1 -1
  31. package/dist/artifact-builder/ModelDataBuilder.js +49 -60
  32. package/dist/artifact-builder/ModelDataBuilder.js.map +1 -1
  33. package/dist/artifact-builder/ModelDataRefiner.js +11 -7
  34. package/dist/artifact-builder/ModelDataRefiner.js.map +1 -1
  35. package/dist/artifact-builder/SchemaGenerator.js +0 -2
  36. package/dist/artifact-builder/SchemaGenerator.js.map +1 -1
  37. package/dist/artifact-builder/ScreenInterfaceRefiner.js +0 -5
  38. package/dist/artifact-builder/ScreenInterfaceRefiner.js.map +1 -1
  39. package/dist/artifact-builder/SelectorBuilder.js +3 -1
  40. package/dist/artifact-builder/SelectorBuilder.js.map +1 -1
  41. package/dist/artifact-builder/SubjectComponentGenerator.d.ts +4 -0
  42. package/dist/artifact-builder/SubjectComponentGenerator.js +61 -5
  43. package/dist/artifact-builder/SubjectComponentGenerator.js.map +1 -1
  44. package/dist/artifact-builder/SubjectGenerator.js +1 -1
  45. package/dist/artifact-builder/SubjectGenerator.js.map +1 -1
  46. package/dist/artifact-builder/SubjectRegistration.d.ts +9 -10
  47. package/dist/artifact-builder/SubjectRegistration.js +51 -43
  48. package/dist/artifact-builder/SubjectRegistration.js.map +1 -1
  49. package/dist/artifact-builder/helpers/DataRetriever.js +4 -3
  50. package/dist/artifact-builder/helpers/DataRetriever.js.map +1 -1
  51. package/dist/artifact-builder/helpers/NamingHandler.d.ts +2 -1
  52. package/dist/artifact-builder/helpers/NamingHandler.js +23 -16
  53. package/dist/artifact-builder/helpers/NamingHandler.js.map +1 -1
  54. package/dist/artifact-builder/helpers/Stopwatch.d.ts +5 -0
  55. package/dist/artifact-builder/helpers/Stopwatch.js +13 -0
  56. package/dist/artifact-builder/helpers/Stopwatch.js.map +1 -0
  57. package/dist/artifact-builder/helpers/index.d.ts +1 -0
  58. package/dist/artifact-builder/helpers/index.js +1 -0
  59. package/dist/artifact-builder/helpers/index.js.map +1 -1
  60. package/dist/components/BaseComponentObjects.d.ts +1 -0
  61. package/dist/components/BaseComponentObjects.js +3 -1
  62. package/dist/components/BaseComponentObjects.js.map +1 -1
  63. package/dist/components/action-bar/ActionBar.d.ts +1 -1
  64. package/dist/components/action-bar/ActionBar.js +3 -3
  65. package/dist/components/grid/Grid.d.ts +5 -4
  66. package/dist/components/grid/Grid.js +13 -19
  67. package/dist/components/grid/Grid.js.map +1 -1
  68. package/dist/components/tab/BaseTab.d.ts +2 -2
  69. package/dist/components/tab/BaseTab.js +2 -2
  70. package/dist/components/tab/BaseTab.js.map +1 -1
  71. package/dist/components/tab/BaseTabObjects.js +1 -1
  72. package/dist/components/tab/BaseTabObjects.js.map +1 -1
  73. package/dist/components/tab/DetailTabPage.d.ts +2 -2
  74. package/dist/components/tab/DetailTabPage.js +2 -2
  75. package/dist/components/tab/DetailTabPage.js.map +1 -1
  76. package/dist/components/tab/Tab.d.ts +3 -3
  77. package/dist/components/tab/Tab.js +4 -4
  78. package/dist/components/tab/Tab.js.map +1 -1
  79. package/dist/controls/LookupDropdown.d.ts +3 -7
  80. package/dist/controls/LookupDropdown.js.map +1 -1
  81. package/dist/enums/ElementTypes.d.ts +1 -1
  82. package/dist/enums/ElementTypes.js +1 -1
  83. package/dist/enums/ElementTypes.js.map +1 -1
  84. package/dist/helpers/ConfigChecker.d.ts +3 -0
  85. package/dist/helpers/ConfigChecker.js +7 -0
  86. package/dist/helpers/ConfigChecker.js.map +1 -0
  87. package/dist/helpers/LoginHelper.js +1 -1
  88. package/dist/helpers/LoginHelper.js.map +1 -1
  89. package/dist/interfaces/IComponentObjects.d.ts +1 -0
  90. package/dist/page-extensions/SubjectRegistry.d.ts +0 -8
  91. package/dist/page-extensions/SubjectRegistry.js +2 -6
  92. package/dist/page-extensions/SubjectRegistry.js.map +1 -1
  93. package/dist/page-extensions/index.d.ts +0 -1
  94. package/dist/page-extensions/index.js +0 -1
  95. package/dist/page-extensions/index.js.map +1 -1
  96. package/dist/services/IndiciumApi.service.d.ts +27 -0
  97. package/dist/services/IndiciumApi.service.js +135 -0
  98. package/dist/services/IndiciumApi.service.js.map +1 -0
  99. package/dist/templates/test-artifacts/SubjectPageBase.d.ts +5 -0
  100. package/dist/templates/test-artifacts/SubjectPageBase.js +6 -0
  101. package/dist/templates/test-artifacts/SubjectPageBase.js.map +1 -0
  102. package/dist/templates/test-artifacts/screens/index.d.ts +1 -0
  103. package/dist/templates/test-artifacts/screens/index.js +2 -0
  104. package/dist/templates/test-artifacts/screens/index.js.map +1 -0
  105. package/dist/templates/test-artifacts/subjects/index.d.ts +1 -0
  106. package/dist/templates/test-artifacts/subjects/index.js +2 -0
  107. package/dist/templates/test-artifacts/subjects/index.js.map +1 -0
  108. package/enums/ElementTypes.ts +2 -2
  109. package/helpers/ConfigChecker.ts +7 -0
  110. package/helpers/LoginHelper.ts +1 -1
  111. package/interfaces/IComponentObjects.ts +1 -0
  112. package/interfaces/IRegisteredSubjects.ts +1 -1
  113. package/package.json +5 -3
  114. package/page-extensions/SubjectRegistry.ts +2 -19
  115. package/page-extensions/index.ts +0 -1
  116. package/scripts/main.js +63 -82
  117. package/scripts/postinstall.js +40 -42
  118. package/scripts/setup.js +37 -37
  119. package/scripts/sync.js +756 -69
  120. package/services/ConfigBuilder.ts +1 -1
  121. package/services/IndiciumApi.service.ts +159 -0
  122. package/templates/SubjectRegistry.template.ts +73 -0
  123. package/templates/test-artifacts/SubjectPageBase.ts +9 -0
  124. package/templates/test-artifacts/screens/index.ts +0 -0
  125. package/templates/test-artifacts/subjects/index.ts +0 -0
  126. package/tsconfig.json +2 -3
  127. package/types/Components.ts +1 -1
  128. package/dist/config.json +0 -10
  129. package/dist/page-extensions/SubjectProvider.d.ts +0 -11
  130. package/dist/page-extensions/SubjectProvider.js +0 -24
  131. package/dist/page-extensions/SubjectProvider.js.map +0 -1
  132. package/dist/test-artifacts/index.d.ts +0 -3
  133. package/dist/test-artifacts/index.js +0 -4
  134. package/dist/test-artifacts/index.js.map +0 -1
  135. package/page-extensions/SubjectProvider.ts +0 -41
  136. package/test-artifacts/index.ts +0 -3
package/Testwise.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { test as base } from '@playwright/test';
2
+ import { test as bddBase } from 'playwright-bdd';
2
3
  import { Components, GoToDeepLink, LoginFeatures, UserSimulation } from './page-extensions/index.js';
3
- import { SubjectProvider } from './page-extensions/SubjectProvider.js';
4
4
  import { SubjectRegistry } from './page-extensions/SubjectRegistry.js';
5
5
  import { WaitEventHandler } from './page-extensions/WaitEventHandler.js';
6
6
  import { ClickOverride } from './page-overrides/ClickOverride.js';
@@ -11,9 +11,7 @@ function combineExtensions(baseTest: Test, ...extensions: { new (test: Test): {
11
11
  return extensions.reduce((test, Extension) => new Extension(test).test, baseTest);
12
12
  }
13
13
 
14
- export const test: Test = combineExtensions(
15
- base,
16
-
14
+ const extensions = [
17
15
  // Override section
18
16
  ClickOverride,
19
17
  FillOverride,
@@ -24,6 +22,9 @@ export const test: Test = combineExtensions(
24
22
  LoginFeatures,
25
23
  UserSimulation,
26
24
  WaitEventHandler,
27
- SubjectRegistry,
28
- SubjectProvider
29
- );
25
+ SubjectRegistry
26
+ ];
27
+
28
+ export const test: Test = combineExtensions(base, ...extensions);
29
+
30
+ export const bddTest: Test = combineExtensions(bddBase, ...extensions);
@@ -1,4 +1,3 @@
1
-
2
1
  import * as fs from 'node:fs';
3
2
  import * as path from 'node:path';
4
3
  import { fileURLToPath } from 'node:url';
@@ -1,20 +1,22 @@
1
-
2
- import { exec } from 'node:child_process';
3
1
  import fs from 'node:fs';
4
2
  import path from 'node:path';
5
3
  import { fileURLToPath } from 'node:url';
4
+ import * as prettier from 'prettier';
5
+ import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } from 'quicktype-core';
6
6
  import { PathResolver } from '../helpers/PathResolver.js';
7
- import { NamingHandler } from '../index.js';
7
+ import { DataRetriever, NamingHandler } from '../index.js';
8
8
 
9
9
  export class InterfaceGenerator {
10
10
  private readonly _namingHandler: NamingHandler;
11
11
  private _filename: string;
12
12
  private _dirname: string;
13
+ private _screensToBuild: string[];
13
14
 
14
15
  constructor() {
15
16
  this._namingHandler = new NamingHandler();
16
17
  this._filename = fileURLToPath(import.meta.url);
17
18
  this._dirname = path.dirname(this._filename);
19
+ this._screensToBuild = new DataRetriever().getScreensToBuild();
18
20
  }
19
21
 
20
22
  public async generateScreenInterfacesFromSchemas() {
@@ -24,7 +26,6 @@ export class InterfaceGenerator {
24
26
  const schemasDirectory = this.getScreenSchemaFilesPath();
25
27
 
26
28
  await this.generateInterfacesFromSchemaFiles(schemasDirectory, SchemaType.Screen, screenOutputDirectory);
27
- await this.runPrettierOnSpecifiedDirectory(screenOutputDirectory);
28
29
 
29
30
  console.info('All screen schemas processed.');
30
31
  }
@@ -37,21 +38,18 @@ export class InterfaceGenerator {
37
38
  this.verifyOutputDirectory(subjectOutputDirectory);
38
39
 
39
40
  await this.generateInterfacesFromSchemaFiles(schemasDirectory, SchemaType.Subject, subjectOutputDirectory);
40
- await this.runPrettierOnSpecifiedDirectory(subjectOutputDirectory);
41
41
 
42
42
  console.info('All subject schemas processed.');
43
43
  }
44
44
 
45
45
  private verifyGeneratedSubjectSchemasDirectory(directory: string) {
46
46
  if (!fs.existsSync(directory)) {
47
- console.error(`Schemas directory not found: ${directory}`);
48
47
  return;
49
48
  }
50
49
  }
51
50
 
52
51
  private verifyOutputDirectory(directory: string) {
53
52
  if (!fs.existsSync(directory)) {
54
- console.info(`Creating output directory: ${directory}`);
55
53
  fs.mkdirSync(directory, { recursive: true });
56
54
  }
57
55
  }
@@ -69,51 +67,58 @@ export class InterfaceGenerator {
69
67
  return componentsDirectory;
70
68
  }
71
69
 
72
- private createInterfaceFromSchema(
70
+ private async createInterfaceFromSchema(
73
71
  schemasDirectory: string,
74
72
  outputPath: string,
75
73
  file: string,
76
74
  isInterface: boolean = false
77
75
  ): Promise<boolean> {
78
- return new Promise((resolve, reject) => {
79
- exec(
80
- `quicktype --just-types --lang typescript --src-lang schema --nice-property-names -o "${outputPath}" "${file}"`,
81
- { cwd: schemasDirectory },
82
- (error) => {
83
- if (error) {
84
- console.error(`Error processing ${file}:`, error.message);
85
- reject(error);
86
- return;
87
- }
88
- // This is an ugly patch to fix interface names after quicktype generation due to quicktype applying naming logic to acronyms that we can't switch off
89
- try {
90
- const fileName = path.basename(outputPath, '.ts');
91
- const fileContent = fs.readFileSync(outputPath, 'utf-8');
92
- const interfaceNameMatch = fileContent.match(new RegExp(`interface ${fileName}`, 'i'));
93
-
94
- if (interfaceNameMatch) {
95
- const correctInterfaceName = isInterface
96
- ? this._namingHandler.generateScreenInterfaceName(file.slice(0, file.lastIndexOf('.')))
97
- : this._namingHandler.snakeToPascalCase(file.slice(0, file.lastIndexOf('.')));
98
- const updatedContent = fileContent.replace(
99
- new RegExp(`interface ${interfaceNameMatch[0].split(' ')[1]}`),
100
- `interface ${correctInterfaceName}`
101
- );
102
- fs.writeFileSync(outputPath, updatedContent, 'utf-8');
103
- }
104
- } catch (fsError) {
105
- console.error(
106
- `Error post-processing ${outputPath}:`,
107
- fsError instanceof Error ? fsError.message : String(fsError)
108
- );
109
- reject(fsError);
110
- return;
111
- }
112
- console.info(`Generated: ${outputPath}`);
113
- resolve(true);
114
- }
115
- );
116
- });
76
+ try {
77
+ const baseFileName = file.slice(0, file.lastIndexOf('.'));
78
+ const correctInterfaceName = isInterface
79
+ ? this._namingHandler.generateScreenInterfaceName(baseFileName)
80
+ : this._namingHandler.snakeToPascalCase(baseFileName);
81
+
82
+ const schemaPath = path.join(schemasDirectory, file);
83
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
84
+
85
+ const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
86
+ await schemaInput.addSource({
87
+ name: correctInterfaceName,
88
+ schema: schemaContent
89
+ });
90
+
91
+ const inputData = new InputData();
92
+ inputData.addInput(schemaInput);
93
+
94
+ const result = await quicktype({
95
+ inputData,
96
+ lang: 'typescript',
97
+ rendererOptions: { 'just-types': 'true', 'nice-property-names': 'true' }
98
+ });
99
+
100
+ let updatedContent = result.lines.join('\n');
101
+
102
+ updatedContent = updatedContent.replace(/interface\s+\w+/, `interface ${correctInterfaceName}`);
103
+
104
+ const formattedContent = await prettier.format(updatedContent, {
105
+ parser: 'typescript',
106
+ singleQuote: true,
107
+ semi: true,
108
+ trailingComma: 'all'
109
+ });
110
+
111
+ fs.writeFileSync(outputPath, formattedContent, 'utf-8');
112
+
113
+ return true;
114
+ } catch (error) {
115
+ if (error instanceof Error) {
116
+ console.error(`Error processing ${file}:`, error.message);
117
+ } else {
118
+ console.error(`Error processing ${file}:`, error);
119
+ }
120
+ throw error;
121
+ }
117
122
  }
118
123
 
119
124
  private async generateInterfacesFromSchemaFiles(
@@ -123,46 +128,31 @@ export class InterfaceGenerator {
123
128
  ): Promise<void> {
124
129
  const schemaFiles: string[] = this.getGeneratedSubjectSchemas(schemasDirectory);
125
130
 
126
- const quicktypePromises = schemaFiles.map((file) => {
127
- return new Promise((resolve, reject) => {
128
- const fileName = this._namingHandler.getPascalSubjectComponentName(file);
129
-
130
- if (schemaType === SchemaType.Subject) {
131
- const subject = this._namingHandler.getSubjectFromFileName(fileName);
132
- const componentsDirectory = this.verifySubjectComponentsDirectory(outputPath, subject);
133
- const perFileOutputPath = path.join(componentsDirectory, `${fileName}.ts`);
134
- this.createInterfaceFromSchema(schemasDirectory, perFileOutputPath, file).then(resolve).catch(reject);
135
- } else if (schemaType === SchemaType.Screen) {
136
- const perFileOutputPath = path.join(outputPath, `I${fileName}.ts`);
137
- this.createInterfaceFromSchema(schemasDirectory, perFileOutputPath, file, true).then(resolve).catch(reject);
138
- } else {
139
- reject(new Error(`Unsupported schema type: ${schemaType}`));
140
- }
141
- });
142
- });
131
+ const tasks = schemaFiles.map(async (file) => {
132
+ const fileName = this._namingHandler.getPascalSubjectComponentName(file);
143
133
 
144
- await Promise.all(quicktypePromises);
145
- }
134
+ if (schemaType === SchemaType.Subject) {
135
+ const subject = this._namingHandler.getSubjectFromFileName(fileName);
136
+ const componentsDirectory = this.verifySubjectComponentsDirectory(outputPath, subject);
137
+ const perFileOutputPath = path.join(componentsDirectory, `${fileName}.ts`);
138
+ return this.createInterfaceFromSchema(schemasDirectory, perFileOutputPath, file);
139
+ }
140
+
141
+ if (schemaType === SchemaType.Screen) {
142
+ const fileNameSnakeCase = this._namingHandler.pascalToSnakeCase(fileName);
146
143
 
147
- private async runPrettierOnSpecifiedDirectory(directory: string): Promise<void> {
148
- const packageDir = path.resolve(this._dirname, '..');
149
- const relativeDir = path.relative(packageDir, directory);
150
-
151
- await new Promise((resolve, reject) => {
152
- exec(
153
- `npx prettier --write "${relativeDir}"`,
154
- { cwd: packageDir },
155
- (prettierError) => {
156
- if (prettierError) {
157
- console.error(`Prettier failed for ${directory}:`, prettierError.message);
158
- reject(prettierError);
159
- return;
160
- }
161
- console.info(`Formatted all files in: ${directory}`);
162
- resolve(true);
144
+ if (this._screensToBuild.length > 0 && !this._screensToBuild.includes(fileNameSnakeCase)) {
145
+ return Promise.resolve(true);
163
146
  }
164
- );
147
+
148
+ const perFileOutputPath = path.join(outputPath, `I${fileName}.ts`);
149
+ return this.createInterfaceFromSchema(schemasDirectory, perFileOutputPath, file, true);
150
+ }
151
+
152
+ throw new Error(`Unsupported schema type: ${schemaType}`);
165
153
  });
154
+
155
+ await Promise.all(tasks);
166
156
  }
167
157
 
168
158
  public getScreenSchemaFilesPath(): string {
@@ -1,11 +1,13 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /** biome-ignore-all lint/style/noNonNullAssertion: Because I can */
3
3
  /** biome-ignore-all lint/suspicious/noExplicitAny: Because I can */
4
- import axios from 'axios';
4
+ import * as fs from 'node:fs/promises';
5
+ import * as path from 'node:path';
5
6
  import type { IProperty } from '../interfaces/IProperty.js';
6
- import { testwiseConfig } from '../services/ConfigBuilder.js';
7
+ import { indiciumApi } from '../services/IndiciumApi.service.js';
7
8
  import { DataRetriever } from './helpers/DataRetriever.js';
8
9
 
10
+ // ToDo: move this to a types folder
9
11
  type Subjects = {
10
12
  subject: string;
11
13
  variant: string;
@@ -14,82 +16,92 @@ type Subjects = {
14
16
  properties?: IProperty[];
15
17
  };
16
18
 
17
- const serviceUrl: string = testwiseConfig().get<string>('environmentSettings.serviceUrl')!.replace(/\/$/, '');
18
- const metaEndpoint: string = testwiseConfig()
19
- .get<string>('environmentSettings.metaEndpoint')!
20
- .replace(/^\//, '')
21
- .replace(/\/$/, '');
22
- const authUser: string = testwiseConfig().get<string>('environmentSettings.authUser')!;
23
- const authUserPassword: string = testwiseConfig().get<string>('environmentSettings.authUserPassword')!;
24
-
25
- const axiosInstance = axios.create({
26
- baseURL: serviceUrl,
27
- headers: {
28
- Authorization: `Basic ${Buffer.from(`${authUser}:${authUserPassword}`).toString('base64')}`,
29
- accept: '*/*'
30
- }
31
- });
32
-
33
- const guiApplId: number = await getGuiApplId();
34
-
35
19
  export async function buildSubjects() {
36
- const response = await axiosInstance.get(`/iam/${metaEndpoint}/i_ui_tab?$filter=gui_appl_id%20eq%20${guiApplId}`);
37
-
38
- const rawTabs: any[] = response.data?.value || [];
39
-
40
- const subjectArray: Subjects[] = rawTabs.flatMap((tab: any) => {
41
- const base = {
42
- subject: tab.tab_id,
43
- variant: tab.tab_variant_id
44
- };
45
-
46
- return [
47
- { ...base, screentype_id: tab.main_screen_type_id, screentype_context: 'main' },
48
- { ...base, screentype_id: tab.detail_screen_type_id, screentype_context: 'detail' },
49
- { ...base, screentype_id: tab.zoom_screen_type_id, screentype_context: 'zoom' },
50
- { ...base, screentype_id: tab.popup_screen_type_id, screentype_context: 'popup' }
51
- ];
52
- }).filter(item => item.screentype_id !== "not_visible_in_gui");
20
+ if (!(await indiciumApi.canConnectToProject())) {
21
+ console.log('Cannot connect to Indicium API. Aborting subject build.');
22
+ return;
23
+ }
53
24
 
54
- const colResponse = await axiosInstance.get(
55
- `/iam/${metaEndpoint}/i_ui_col?$filter=gui_appl_id%20eq%20${guiApplId}`
56
- );
25
+ const rawTables: any[] = await indiciumApi.getAllTables();
26
+
27
+ const subjects: Subjects[] = rawTables
28
+ .flatMap((tab: any) => {
29
+ const base = {
30
+ subject: tab.tab_id,
31
+ variant: tab.tab_variant_id
32
+ };
33
+
34
+ return [
35
+ { ...base, screentype_id: tab.main_screen_type_id, screentype_context: 'main' },
36
+ { ...base, screentype_id: tab.detail_screen_type_id, screentype_context: 'detail' },
37
+ { ...base, screentype_id: tab.popup_screen_type_id, screentype_context: 'popup' },
38
+ { ...base, screentype_id: tab.zoom_screen_type_id, screentype_context: 'zoom' }
39
+ ];
40
+ })
41
+ .filter((item) => item.screentype_id !== 'not_visible_in_gui');
42
+
43
+ const screentypeOrder = ['main', 'detail', 'popup', 'zoom'];
44
+
45
+ subjects.sort((a, b) => {
46
+ return (
47
+ a.subject.localeCompare(b.subject) ||
48
+ a.variant.localeCompare(b.variant) ||
49
+ screentypeOrder.indexOf(a.screentype_context) - screentypeOrder.indexOf(b.screentype_context)
50
+ );
51
+ });
57
52
 
58
- const allProperties: IPropertyWithSubjectIdentifiers[] = colResponse.data?.value.map((col: any) => ({
53
+ const allProperties: IPropertyWithSubjectIdentifiers[] = (await indiciumApi.getAllColumns()).map((col: any) => ({
59
54
  subject: col.tab_id,
60
55
  variant: col.tab_variant_id,
61
56
  col: col.col_id,
62
57
  control_type: col.control_id || ''
63
58
  }));
64
59
 
65
- for (const subject of subjectArray) {
60
+ for (const subject of subjects) {
66
61
  const matchingProperties = allProperties.filter(
67
62
  (prop) => prop.subject === subject.subject && prop.variant === subject.variant
68
63
  );
69
64
 
70
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
71
- subject.properties = matchingProperties.map(({ subject, variant, ...rest }) => rest);
65
+ subject.properties = matchingProperties
66
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
67
+ .map(({ subject, variant, ...rest }) => rest)
68
+ .sort((a, b) => a.col.localeCompare(b.col));
72
69
  }
73
70
 
74
- const seedDataDirectory = new DataRetriever().getSeedDataDirectory();
71
+ const subjectsWithLookupsSet = await setLookupDropdowns(subjects);
75
72
 
76
- const fs = await import('node:fs').then(mod => mod.promises);
77
- const path = await import('node:path');
73
+ const seedDataDirectory = new DataRetriever().getSeedDataDirectory();
78
74
 
79
75
  const seedDataPath = path.join(seedDataDirectory, 'subjects.json');
80
- await fs.writeFile(seedDataPath, JSON.stringify(subjectArray, null, 2), 'utf-8');
76
+ await fs.writeFile(seedDataPath, JSON.stringify(subjectsWithLookupsSet, null, 2), 'utf-8');
81
77
  console.log(`Seed data written to ${seedDataPath}`);
82
78
  }
83
79
 
84
- import * as fs from 'node:fs/promises';
85
- import * as path from 'node:path';
80
+ async function setLookupDropdowns(subjects: Subjects[]): Promise<Subjects[]> {
81
+ const lookupMappings: any[] = await indiciumApi.getLookups();
82
+
83
+ for (const mapping of lookupMappings) {
84
+ const targetSubject = subjects.find(
85
+ (subj) => subj.subject === mapping.tab_id && subj.variant === mapping.tab_variant_id
86
+ );
87
+
88
+ const targetProperty = targetSubject?.properties?.find((prop) => prop.col === mapping.col_id);
89
+
90
+ if (targetProperty) {
91
+ targetProperty.control_type = 'LOOKUP_DROPDOWN';
92
+ }
93
+ }
94
+
95
+ return subjects;
96
+ }
86
97
 
87
98
  export async function buildScreens() {
88
- const response = await axiosInstance.get(
89
- `/iam/${metaEndpoint}/i_ui_screen_component?$filter=gui_appl_id eq ${guiApplId}`
90
- );
99
+ if (!(await indiciumApi.canConnectToProject())) {
100
+ console.log('Cannot connect to Indicium API. Aborting screen build.');
101
+ return;
102
+ }
91
103
 
92
- const rawComponents = response.data?.value || [];
104
+ const rawComponents = await indiciumApi.getScreenComponents();
93
105
  const screenMap = new Map<string, any>();
94
106
 
95
107
  for (const item of rawComponents) {
@@ -119,59 +131,35 @@ export async function buildScreens() {
119
131
 
120
132
  for (const comp of screenData.Components) {
121
133
  const type = comp.ScreenComponentType;
122
- properties[comp.ScreenComponentId] = { "$ref": `#/definitions/${type}` };
134
+ properties[comp.ScreenComponentId] = { $ref: `#/definitions/${type}` };
123
135
  required.push(comp.ScreenComponentId);
124
136
  usedTypes.add(type);
125
137
  }
126
138
 
127
139
  const schema = {
128
140
  title: `I_${screenTypeId}`,
129
- type: "object",
141
+ type: 'object',
130
142
  properties,
131
143
  required,
132
144
  additionalProperties: false,
133
145
  definitions: Array.from(usedTypes).reduce((acc, type) => {
134
146
  acc[type] = {
135
- type: "object",
136
- properties: { temp: { type: "string" } },
137
- required: ["temp"],
147
+ type: 'object',
148
+ properties: { temp: { type: 'string' } },
149
+ required: ['temp'],
138
150
  additionalProperties: false
139
151
  };
140
152
  return acc;
141
153
  }, {} as any)
142
154
  };
143
155
 
144
- await fs.writeFile(
145
- path.join(outputDir, `${screenTypeId}.json`),
146
- JSON.stringify(schema, null, 2)
147
- );
156
+ await fs.writeFile(path.join(outputDir, `${screenTypeId}.json`), JSON.stringify(schema, null, 2));
148
157
  }
149
158
  console.log('Screen schemas generated successfully.');
150
159
  }
151
160
 
152
- async function getGuiApplId(): Promise<number> {
153
- const guiApplAlias = testwiseConfig().get<string>('environmentSettings.guiApplAlias');
154
-
155
- if (guiApplAlias) {
156
- const response = await axiosInstance.get(`/iam/${metaEndpoint}/i_ui_gui_appl`);
157
- const guiAppl = response.data?.value.find((appl: any) => {
158
- const currentAlias = appl?.gui_appl_alias?.toString();
159
- const targetAlias = guiApplAlias?.toString();
160
-
161
- return currentAlias === targetAlias;
162
- });
163
-
164
- if (guiAppl) {
165
- return guiAppl.gui_appl_id;
166
- } else {
167
- throw new Error(`No GUI Application found for GUI Application Alias: ${guiApplAlias}`);
168
- }
169
- } else {
170
- throw new Error('GUI Application Alias is not defined in the configuration.');
171
- }
172
- }
173
-
161
+ // ToDo: move this to a interfaces folder
174
162
  interface IPropertyWithSubjectIdentifiers extends IProperty {
175
163
  subject: string;
176
164
  variant: string;
177
- }
165
+ }
@@ -8,31 +8,36 @@ export class ModelDataRefiner {
8
8
 
9
9
  public run() {
10
10
  const seedDataDir = this._dataRetriever.getSeedDataDirectory();
11
- const registeredSubjects : IRegisteredSubjects[] | null = this._dataRetriever.getRegisteredSubjects();
11
+ const subjectsToBuildPath = path.resolve(seedDataDir, 'subjectsToBuild.json');
12
+ const screensToBuildPath = path.resolve(seedDataDir, 'screensToBuild.json');
13
+ const registeredSubjects: IRegisteredSubjects[] | null = this._dataRetriever.getRegisteredSubjects();
12
14
 
13
15
  if (!registeredSubjects || registeredSubjects.length === 0) {
14
- console.error('No registered subjects found.');
16
+ console.info('No registered subjects found.');
17
+
18
+ if (fs.existsSync(screensToBuildPath)) fs.unlinkSync(screensToBuildPath);
19
+ if (fs.existsSync(subjectsToBuildPath)) fs.unlinkSync(subjectsToBuildPath);
20
+
15
21
  return;
16
22
  }
17
23
 
18
24
  const subjects = this._dataRetriever.getSubjectsSeedData();
19
25
 
20
- const subjectsToBuild = subjects.filter(subject =>
21
- registeredSubjects.some(registered =>
22
- registered.subject === subject.subject &&
23
- (registered.variant != null ? registered.variant === subject.variant : subject.variant === '') &&
24
- registered.screen_type === subject.screentype_context
26
+ const subjectsToBuild = subjects.filter((subject) =>
27
+ registeredSubjects.some(
28
+ (registered) =>
29
+ registered.subject === subject.subject &&
30
+ (registered.variant != null ? registered.variant === subject.variant : subject.variant === '') &&
31
+ registered.screen_type === subject.screentype_context
25
32
  )
26
33
  );
27
34
 
28
- const subjectsToBuildPath = path.resolve(seedDataDir, 'subjectsToBuild.json');
29
35
  fs.writeFileSync(subjectsToBuildPath, JSON.stringify(subjectsToBuild, null, 2));
30
36
  console.log(`Created subjectsToBuild.json with ${subjectsToBuild.length} subjects.`);
31
37
 
32
- const screensToBuild = Array.from(new Set(subjectsToBuild.map(subject => subject.screentype_id)));
38
+ const screensToBuildList = Array.from(new Set(subjectsToBuild.map((subject) => subject.screentype_id)));
33
39
 
34
- const screensToBuildPath = path.resolve(seedDataDir, 'screensToBuild.json');
35
- fs.writeFileSync(screensToBuildPath, JSON.stringify(screensToBuild, null, 2));
36
- console.log(`Created screensToBuild.json with ${screensToBuild.length} screens.`);
40
+ fs.writeFileSync(screensToBuildPath, JSON.stringify(screensToBuildList, null, 2));
41
+ console.log(`Created screensToBuild.json with ${screensToBuildList.length} screens.`);
37
42
  }
38
- }
43
+ }
@@ -1,4 +1,3 @@
1
-
2
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
3
2
  import * as fs from 'node:fs';
4
3
  import * as path from 'node:path';
@@ -48,11 +47,9 @@ export class SchemaGenerator {
48
47
  switch (component) {
49
48
  case 'Form':
50
49
  this.createComponentSchema(subject, 'Form');
51
- console.log(`Generated Form schema for subject: ${subject.subject}`);
52
50
  break;
53
51
  case 'Grid':
54
52
  this.createComponentSchema(subject, 'Grid');
55
- console.log(`Generated Grid schema for subject: ${subject.subject}`);
56
53
  break;
57
54
  }
58
55
  }
@@ -90,7 +87,7 @@ export class SchemaGenerator {
90
87
  const interfaceName = `${subject.subject}_${componentType.toLowerCase()}`;
91
88
  const schemasDir = path.join(currentDirname, '..', '..', 'dist/test-artifacts/schemas');
92
89
  const fileName = `${this._namingHandler.snakeToPascalCase(interfaceName)}.json`;
93
-
90
+
94
91
  if (fs.existsSync(path.join(schemasDir, fileName))) {
95
92
  return;
96
93
  }
@@ -13,8 +13,6 @@ export class ScreenInterfaceRefiner {
13
13
  }
14
14
 
15
15
  public refine() {
16
- console.log('Refining interfaces in:', this._screensDirectory);
17
-
18
16
  const screenFiles = this.getAllScreenInterfaceFiles();
19
17
 
20
18
  screenFiles.forEach((screenFile) => {
@@ -34,9 +32,6 @@ export class ScreenInterfaceRefiner {
34
32
  this.formatContent(screen);
35
33
 
36
34
  fs.writeFileSync(filePath, screen.content, 'utf-8');
37
- console.log(`Refined interfaces in: ${screenFile}`);
38
- } else {
39
- console.log(`No additional interfaces to refine in: ${screenFile}`);
40
35
  }
41
36
 
42
37
  this.indexScreenInterface(screenFile);
@@ -53,7 +53,14 @@ export class SelectorBuilder {
53
53
  }
54
54
 
55
55
  public getPageLocatorString(property: string, componentName: SubjectComponents): string {
56
- const elementId = this._namingHandler.getIdFromElementName(property);
56
+ let elementId = this._namingHandler.getIdFromElementName(
57
+ property,
58
+ componentName,
59
+ this._namingHandler.getElementTypeFromElementName(property)
60
+ );
61
+
62
+ if (componentName === 'Grid') elementId = elementId.replace(/-/g, '_');
63
+
57
64
  const selectorPrefix = this.getDefaultPrefix(componentName);
58
65
  const defaultSuffix = this.getDefaultSuffix(componentName);
59
66
  const selectorSuffix = this.determineSelectorSuffix(property, componentName) || defaultSuffix;