@thinkwise/testwise 0.1.97 → 0.2.0-beta.22

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 (202) hide show
  1. package/Testwise.ts +7 -9
  2. package/artifact-builder/ArtifactManager.ts +33 -0
  3. package/artifact-builder/InterfaceGenerator.ts +165 -0
  4. package/artifact-builder/ModelDataBuilder.ts +194 -0
  5. package/artifact-builder/ModelDataRefiner.ts +39 -0
  6. package/artifact-builder/SchemaGenerator.ts +131 -0
  7. package/artifact-builder/ScreenInterfaceRefiner.ts +154 -0
  8. package/artifact-builder/SelectorBuilder.ts +82 -0
  9. package/artifact-builder/SubjectComponentGenerator.ts +186 -0
  10. package/artifact-builder/SubjectGenerator.ts +332 -0
  11. package/artifact-builder/SubjectRegistration.ts +131 -0
  12. package/artifact-builder/helpers/DataRetriever.ts +64 -0
  13. package/artifact-builder/helpers/NamingHandler.ts +182 -0
  14. package/artifact-builder/helpers/Stopwatch.ts +13 -0
  15. package/artifact-builder/helpers/index.ts +3 -0
  16. package/artifact-builder/index.ts +9 -0
  17. package/components/{actionbar/Actionbar.ts → action-bar/ActionBar.ts} +12 -12
  18. package/components/{actionbar/ActionbarObjects.ts → action-bar/ActionBarObjects.ts} +4 -4
  19. package/components/{actionbar → action-bar}/CustomActionBar.ts +2 -2
  20. package/components/index.ts +3 -4
  21. package/components/tab/DetailTabPage.ts +20 -0
  22. package/components/tab/{DetailTabObjects.ts → DetailTabPageObjects.ts} +1 -1
  23. package/components/tab/Tab.ts +31 -5
  24. package/components/tab/TabObjects.ts +15 -3
  25. package/dist/Testwise.js +5 -1
  26. package/dist/Testwise.js.map +1 -1
  27. package/dist/artifact-builder/ArtifactManager.d.ts +8 -0
  28. package/dist/artifact-builder/ArtifactManager.js +27 -0
  29. package/dist/artifact-builder/ArtifactManager.js.map +1 -0
  30. package/dist/artifact-builder/InterfaceGenerator.d.ts +15 -0
  31. package/dist/artifact-builder/InterfaceGenerator.js +122 -0
  32. package/dist/artifact-builder/InterfaceGenerator.js.map +1 -0
  33. package/dist/artifact-builder/ModelDataBuilder.d.ts +2 -0
  34. package/dist/artifact-builder/ModelDataBuilder.js +141 -0
  35. package/dist/artifact-builder/ModelDataBuilder.js.map +1 -0
  36. package/dist/artifact-builder/ModelDataRefiner.d.ts +4 -0
  37. package/dist/artifact-builder/ModelDataRefiner.js +28 -0
  38. package/dist/artifact-builder/ModelDataRefiner.js.map +1 -0
  39. package/dist/artifact-builder/SchemaGenerator.d.ts +12 -0
  40. package/dist/artifact-builder/SchemaGenerator.js +102 -0
  41. package/dist/artifact-builder/SchemaGenerator.js.map +1 -0
  42. package/dist/artifact-builder/ScreenInterfaceRefiner.d.ts +15 -0
  43. package/dist/artifact-builder/ScreenInterfaceRefiner.js +120 -0
  44. package/dist/artifact-builder/ScreenInterfaceRefiner.js.map +1 -0
  45. package/dist/artifact-builder/SelectorBuilder.d.ts +13 -0
  46. package/dist/artifact-builder/SelectorBuilder.js +69 -0
  47. package/dist/artifact-builder/SelectorBuilder.js.map +1 -0
  48. package/dist/artifact-builder/SubjectComponentGenerator.d.ts +23 -0
  49. package/dist/artifact-builder/SubjectComponentGenerator.js +136 -0
  50. package/dist/artifact-builder/SubjectComponentGenerator.js.map +1 -0
  51. package/dist/artifact-builder/SubjectGenerator.d.ts +27 -0
  52. package/dist/artifact-builder/SubjectGenerator.js +235 -0
  53. package/dist/artifact-builder/SubjectGenerator.js.map +1 -0
  54. package/dist/artifact-builder/SubjectRegistration.d.ts +21 -0
  55. package/dist/artifact-builder/SubjectRegistration.js +95 -0
  56. package/dist/artifact-builder/SubjectRegistration.js.map +1 -0
  57. package/dist/artifact-builder/helpers/DataRetriever.d.ts +12 -0
  58. package/dist/artifact-builder/helpers/DataRetriever.js +52 -0
  59. package/dist/artifact-builder/helpers/DataRetriever.js.map +1 -0
  60. package/dist/artifact-builder/helpers/NamingHandler.d.ts +24 -0
  61. package/dist/artifact-builder/helpers/NamingHandler.js +147 -0
  62. package/dist/artifact-builder/helpers/NamingHandler.js.map +1 -0
  63. package/dist/artifact-builder/helpers/Stopwatch.d.ts +5 -0
  64. package/dist/artifact-builder/helpers/Stopwatch.js +13 -0
  65. package/dist/artifact-builder/helpers/Stopwatch.js.map +1 -0
  66. package/dist/artifact-builder/helpers/index.d.ts +3 -0
  67. package/dist/artifact-builder/helpers/index.js +4 -0
  68. package/dist/artifact-builder/helpers/index.js.map +1 -0
  69. package/dist/artifact-builder/index.d.ts +9 -0
  70. package/dist/artifact-builder/index.js +10 -0
  71. package/dist/artifact-builder/index.js.map +1 -0
  72. package/dist/components/{actionbar/Actionbar.d.ts → action-bar/ActionBar.d.ts} +4 -4
  73. package/dist/components/{actionbar/Actionbar.js → action-bar/ActionBar.js} +9 -9
  74. package/dist/components/action-bar/ActionBar.js.map +1 -0
  75. package/dist/components/{actionbar/ActionbarObjects.d.ts → action-bar/ActionBarObjects.d.ts} +2 -2
  76. package/dist/components/{actionbar/ActionbarObjects.js → action-bar/ActionBarObjects.js} +5 -5
  77. package/dist/components/action-bar/ActionBarObjects.js.map +1 -0
  78. package/dist/components/{actionbar → action-bar}/CustomActionBar.d.ts +2 -2
  79. package/dist/components/action-bar/CustomActionBar.js +7 -0
  80. package/dist/components/action-bar/CustomActionBar.js.map +1 -0
  81. package/dist/components/index.d.ts +3 -4
  82. package/dist/components/index.js +3 -4
  83. package/dist/components/index.js.map +1 -1
  84. package/dist/components/tab/DetailTabPage.d.ts +9 -0
  85. package/dist/components/tab/DetailTabPage.js +15 -0
  86. package/dist/components/tab/DetailTabPage.js.map +1 -0
  87. package/dist/components/tab/{DetailTabObjects.d.ts → DetailTabPageObjects.d.ts} +1 -1
  88. package/dist/components/tab/{DetailTabObjects.js → DetailTabPageObjects.js} +2 -2
  89. package/dist/components/tab/DetailTabPageObjects.js.map +1 -0
  90. package/dist/components/tab/Tab.d.ts +8 -3
  91. package/dist/components/tab/Tab.js +25 -4
  92. package/dist/components/tab/Tab.js.map +1 -1
  93. package/dist/components/tab/TabObjects.d.ts +7 -2
  94. package/dist/components/tab/TabObjects.js +9 -2
  95. package/dist/components/tab/TabObjects.js.map +1 -1
  96. package/dist/enums/ElementTypes.d.ts +8 -0
  97. package/dist/enums/ElementTypes.js +10 -0
  98. package/dist/enums/ElementTypes.js.map +1 -0
  99. package/dist/helpers/PathResolver.d.ts +3 -0
  100. package/dist/helpers/PathResolver.js +26 -0
  101. package/dist/helpers/PathResolver.js.map +1 -0
  102. package/dist/helpers/index.d.ts +1 -0
  103. package/dist/helpers/index.js +1 -0
  104. package/dist/helpers/index.js.map +1 -1
  105. package/dist/index.d.ts +1 -0
  106. package/dist/index.js +1 -0
  107. package/dist/index.js.map +1 -1
  108. package/dist/interfaces/IProperty.d.ts +4 -0
  109. package/dist/interfaces/IProperty.js +2 -0
  110. package/dist/interfaces/IProperty.js.map +1 -0
  111. package/dist/interfaces/IRegisteredSubjects.d.ts +5 -0
  112. package/dist/interfaces/IRegisteredSubjects.js +2 -0
  113. package/dist/interfaces/IRegisteredSubjects.js.map +1 -0
  114. package/dist/interfaces/ISubject.d.ts +8 -0
  115. package/dist/interfaces/ISubject.js +2 -0
  116. package/dist/interfaces/ISubject.js.map +1 -0
  117. package/dist/page-extensions/SubjectProvider.d.ts +11 -0
  118. package/dist/page-extensions/SubjectProvider.js +24 -0
  119. package/dist/page-extensions/SubjectProvider.js.map +1 -0
  120. package/dist/page-extensions/SubjectRegistry.d.ts +14 -0
  121. package/dist/page-extensions/SubjectRegistry.js +14 -0
  122. package/dist/page-extensions/SubjectRegistry.js.map +1 -0
  123. package/dist/page-extensions/index.d.ts +3 -0
  124. package/dist/page-extensions/index.js +3 -0
  125. package/dist/page-extensions/index.js.map +1 -1
  126. package/dist/services/ConfigBuilder.d.ts +1 -0
  127. package/dist/services/ConfigBuilder.js +20 -1
  128. package/dist/services/ConfigBuilder.js.map +1 -1
  129. package/dist/test-artifacts/SubjectPageBase.d.ts +5 -0
  130. package/dist/test-artifacts/SubjectPageBase.js +6 -0
  131. package/dist/test-artifacts/SubjectPageBase.js.map +1 -0
  132. package/dist/test-artifacts/index.d.ts +3 -0
  133. package/dist/test-artifacts/index.js +4 -0
  134. package/dist/test-artifacts/index.js.map +1 -0
  135. package/dist/test-artifacts/screens/index.d.ts +1 -0
  136. package/dist/test-artifacts/screens/index.js +2 -0
  137. package/dist/test-artifacts/screens/index.js.map +1 -0
  138. package/dist/test-artifacts/subjects/index.d.ts +1 -0
  139. package/dist/test-artifacts/subjects/index.js +2 -0
  140. package/dist/test-artifacts/subjects/index.js.map +1 -0
  141. package/dist/types/Components.d.ts +7 -0
  142. package/dist/types/Components.js +28 -0
  143. package/dist/types/Components.js.map +1 -0
  144. package/enums/ElementTypes.ts +8 -0
  145. package/helpers/PathResolver.ts +30 -0
  146. package/helpers/index.ts +1 -0
  147. package/index.ts +1 -0
  148. package/interfaces/IProperty.ts +4 -0
  149. package/interfaces/IRegisteredSubjects.ts +5 -0
  150. package/interfaces/ISubject.ts +9 -0
  151. package/package.json +26 -8
  152. package/page-extensions/SubjectProvider.ts +41 -0
  153. package/page-extensions/SubjectRegistry.ts +30 -0
  154. package/page-extensions/index.ts +3 -0
  155. package/promptCredentials.js +124 -124
  156. package/scripts/Testwise.template.json +4 -1
  157. package/scripts/main.js +84 -11
  158. package/scripts/postinstall.js +39 -0
  159. package/scripts/setup.js +37 -34
  160. package/scripts/sync.js +102 -0
  161. package/scripts/tsconfig.template.json +1 -1
  162. package/services/ConfigBuilder.ts +25 -2
  163. package/test-artifacts/SubjectPageBase.ts +9 -0
  164. package/test-artifacts/index.ts +3 -0
  165. package/test-artifacts/screens/index.ts +0 -0
  166. package/test-artifacts/subjects/index.ts +0 -0
  167. package/tsconfig.json +1 -1
  168. package/types/Components.ts +55 -0
  169. package/components/tab/ComponentTab.ts +0 -40
  170. package/components/tab/ComponentTabObjects.ts +0 -17
  171. package/components/tab/DetailTab.ts +0 -20
  172. package/dist/Testwise.json +0 -25
  173. package/dist/bdd.d.ts +0 -6
  174. package/dist/bdd.js +0 -9
  175. package/dist/bdd.js.map +0 -1
  176. package/dist/biome.json +0 -52
  177. package/dist/components/actionbar/Actionbar.js.map +0 -1
  178. package/dist/components/actionbar/ActionbarObjects.js.map +0 -1
  179. package/dist/components/actionbar/CustomActionBar.js +0 -7
  180. package/dist/components/actionbar/CustomActionBar.js.map +0 -1
  181. package/dist/components/tab/ComponentTab.d.ts +0 -12
  182. package/dist/components/tab/ComponentTab.js +0 -31
  183. package/dist/components/tab/ComponentTab.js.map +0 -1
  184. package/dist/components/tab/ComponentTabObjects.d.ts +0 -8
  185. package/dist/components/tab/ComponentTabObjects.js +0 -11
  186. package/dist/components/tab/ComponentTabObjects.js.map +0 -1
  187. package/dist/components/tab/DetailTab.d.ts +0 -9
  188. package/dist/components/tab/DetailTab.js +0 -15
  189. package/dist/components/tab/DetailTab.js.map +0 -1
  190. package/dist/components/tab/DetailTabObjects.js.map +0 -1
  191. package/dist/helpers/TestExtensions.d.ts +0 -8
  192. package/dist/helpers/TestExtensions.js +0 -21
  193. package/dist/helpers/TestExtensions.js.map +0 -1
  194. package/dist/package-lock.json +0 -3852
  195. package/dist/package.json +0 -59
  196. package/dist/scripts/Testwise.template.json +0 -25
  197. package/dist/scripts/tsconfig.template.json +0 -12
  198. package/dist/services/ReportingService.d.ts +0 -8
  199. package/dist/services/ReportingService.js +0 -29
  200. package/dist/services/ReportingService.js.map +0 -1
  201. package/dist/tsconfig.json +0 -20
  202. package/services/ReportingService.ts +0 -37
package/Testwise.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { test as base } from '@playwright/test';
2
2
  import { test as bddBase } from 'playwright-bdd';
3
3
  import { Components, GoToDeepLink, LoginFeatures, UserSimulation } from './page-extensions/index.js';
4
+ import { SubjectProvider } from './page-extensions/SubjectProvider.js';
5
+ import { SubjectRegistry } from './page-extensions/SubjectRegistry.js';
4
6
  import { WaitEventHandler } from './page-extensions/WaitEventHandler.js';
5
7
  import { ClickOverride } from './page-overrides/ClickOverride.js';
6
8
  import { FillOverride } from './page-overrides/FillOverride.js';
@@ -20,15 +22,11 @@ const extensions = [
20
22
  GoToDeepLink,
21
23
  LoginFeatures,
22
24
  UserSimulation,
23
- WaitEventHandler
25
+ WaitEventHandler,
26
+ SubjectRegistry,
27
+ SubjectProvider
24
28
  ];
25
29
 
26
- export const test: Test = combineExtensions(
27
- base,
28
- ...extensions
29
- );
30
+ export const test: Test = combineExtensions(base, ...extensions);
30
31
 
31
- export const bddTest: Test = combineExtensions(
32
- bddBase,
33
- ...extensions
34
- );
32
+ export const bddTest: Test = combineExtensions(bddBase, ...extensions);
@@ -0,0 +1,33 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ export class ArtifactManager {
6
+ private _backupPath: string;
7
+ private _artifactPath: string;
8
+
9
+ constructor() {
10
+ const _filename = fileURLToPath(import.meta.url);
11
+ const _dirname = path.dirname(_filename);
12
+ this._backupPath = path.resolve(_dirname, '..', '..', '..', 'backups', 'test-artifacts');
13
+ this._artifactPath = path.resolve(_dirname, '..', '..', 'test-artifacts');
14
+ }
15
+
16
+ performBackup() {
17
+ if (fs.existsSync(this._artifactPath)) {
18
+ fs.rmSync(this._backupPath, { recursive: true, force: true });
19
+ fs.cpSync(this._artifactPath, this._backupPath, { recursive: true });
20
+ }
21
+ }
22
+
23
+ backupExists(): boolean {
24
+ return fs.existsSync(this._backupPath);
25
+ }
26
+
27
+ restoreBackup(): void {
28
+ if (fs.existsSync(this._backupPath)) {
29
+ fs.rmSync(this._artifactPath, { recursive: true, force: true });
30
+ fs.cpSync(this._backupPath, this._artifactPath, { recursive: true });
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,165 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import * as prettier from 'prettier';
5
+ import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } from 'quicktype-core';
6
+ import { PathResolver } from '../helpers/PathResolver.js';
7
+ import { NamingHandler } from '../index.js';
8
+
9
+ export class InterfaceGenerator {
10
+ private readonly _namingHandler: NamingHandler;
11
+ private _filename: string;
12
+ private _dirname: string;
13
+
14
+ constructor() {
15
+ this._namingHandler = new NamingHandler();
16
+ this._filename = fileURLToPath(import.meta.url);
17
+ this._dirname = path.dirname(this._filename);
18
+ }
19
+
20
+ public async generateScreenInterfacesFromSchemas() {
21
+ const screenOutputDirectory = path.resolve(this._dirname, '..', '..', 'test-artifacts/screens/');
22
+ this.verifyOutputDirectory(screenOutputDirectory);
23
+
24
+ const schemasDirectory = this.getScreenSchemaFilesPath();
25
+
26
+ await this.generateInterfacesFromSchemaFiles(schemasDirectory, SchemaType.Screen, screenOutputDirectory);
27
+
28
+ console.info('All screen schemas processed.');
29
+ }
30
+
31
+ public async generateSubjectInterfacesFromSchemas() {
32
+ const schemasDirectory = path.resolve(this._dirname, '../test-artifacts/schemas');
33
+ const subjectOutputDirectory = path.resolve(this._dirname, '..', '..', 'test-artifacts/subjects');
34
+
35
+ this.verifyGeneratedSubjectSchemasDirectory(schemasDirectory);
36
+ this.verifyOutputDirectory(subjectOutputDirectory);
37
+
38
+ await this.generateInterfacesFromSchemaFiles(schemasDirectory, SchemaType.Subject, subjectOutputDirectory);
39
+
40
+ console.info('All subject schemas processed.');
41
+ }
42
+
43
+ private verifyGeneratedSubjectSchemasDirectory(directory: string) {
44
+ if (!fs.existsSync(directory)) {
45
+ return;
46
+ }
47
+ }
48
+
49
+ private verifyOutputDirectory(directory: string) {
50
+ if (!fs.existsSync(directory)) {
51
+ fs.mkdirSync(directory, { recursive: true });
52
+ }
53
+ }
54
+
55
+ private getGeneratedSubjectSchemas(schemasDirectory: string): string[] {
56
+ return fs.readdirSync(schemasDirectory).filter((f) => f.endsWith('.json'));
57
+ }
58
+
59
+ private verifySubjectComponentsDirectory(directory: string, subject: string) {
60
+ const componentsDirectory = path.join(directory, `${subject}/Components`);
61
+ if (!fs.existsSync(componentsDirectory)) {
62
+ fs.mkdirSync(componentsDirectory, { recursive: true });
63
+ }
64
+
65
+ return componentsDirectory;
66
+ }
67
+
68
+ private async createInterfaceFromSchema(
69
+ schemasDirectory: string,
70
+ outputPath: string,
71
+ file: string,
72
+ isInterface: boolean = false
73
+ ): Promise<boolean> {
74
+ try {
75
+ const baseFileName = file.slice(0, file.lastIndexOf('.'));
76
+ const correctInterfaceName = isInterface
77
+ ? this._namingHandler.generateScreenInterfaceName(baseFileName)
78
+ : this._namingHandler.snakeToPascalCase(baseFileName);
79
+
80
+ const schemaPath = path.join(schemasDirectory, file);
81
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
82
+
83
+ const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
84
+ await schemaInput.addSource({
85
+ name: correctInterfaceName,
86
+ schema: schemaContent
87
+ });
88
+
89
+ const inputData = new InputData();
90
+ inputData.addInput(schemaInput);
91
+
92
+ const result = await quicktype({
93
+ inputData,
94
+ lang: 'typescript',
95
+ rendererOptions: { 'just-types': 'true', 'nice-property-names': 'true' }
96
+ });
97
+
98
+ let updatedContent = result.lines.join('\n');
99
+
100
+ updatedContent = updatedContent.replace(/interface\s+\w+/, `interface ${correctInterfaceName}`);
101
+
102
+ const formattedContent = await prettier.format(updatedContent, {
103
+ parser: 'typescript',
104
+ singleQuote: true,
105
+ semi: true,
106
+ trailingComma: 'all'
107
+ });
108
+
109
+ fs.writeFileSync(outputPath, formattedContent, 'utf-8');
110
+
111
+ return true;
112
+ } catch (error) {
113
+ if (error instanceof Error) {
114
+ console.error(`Error processing ${file}:`, error.message);
115
+ } else {
116
+ console.error(`Error processing ${file}:`, error);
117
+ }
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ private async generateInterfacesFromSchemaFiles(
123
+ schemasDirectory: string,
124
+ schemaType: SchemaType,
125
+ outputPath: string
126
+ ): Promise<void> {
127
+ const schemaFiles: string[] = this.getGeneratedSubjectSchemas(schemasDirectory);
128
+
129
+ const tasks = schemaFiles.map(async (file) => {
130
+ const fileName = this._namingHandler.getPascalSubjectComponentName(file);
131
+
132
+ if (schemaType === SchemaType.Subject) {
133
+ const subject = this._namingHandler.getSubjectFromFileName(fileName);
134
+ const componentsDirectory = this.verifySubjectComponentsDirectory(outputPath, subject);
135
+ const perFileOutputPath = path.join(componentsDirectory, `${fileName}.ts`);
136
+ return this.createInterfaceFromSchema(schemasDirectory, perFileOutputPath, file);
137
+ }
138
+
139
+ if (schemaType === SchemaType.Screen) {
140
+ const perFileOutputPath = path.join(outputPath, `I${fileName}.ts`);
141
+ return this.createInterfaceFromSchema(schemasDirectory, perFileOutputPath, file, true);
142
+ }
143
+
144
+ throw new Error(`Unsupported schema type: ${schemaType}`);
145
+ });
146
+
147
+ await Promise.all(tasks);
148
+ }
149
+
150
+ public getScreenSchemaFilesPath(): string {
151
+ const directory = new PathResolver().getConsumerRootDirectory();
152
+
153
+ const screenSchemasPath = path.join(directory, 'seed-data/screen-schemas/');
154
+ if (!fs.existsSync(screenSchemasPath)) {
155
+ throw new Error(`Screen schemas directory not found at: ${screenSchemasPath}`);
156
+ }
157
+
158
+ return screenSchemasPath;
159
+ }
160
+ }
161
+
162
+ enum SchemaType {
163
+ Subject = 'Subject',
164
+ Screen = 'Screen'
165
+ }
@@ -0,0 +1,194 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /** biome-ignore-all lint/style/noNonNullAssertion: Because I can */
3
+ /** biome-ignore-all lint/suspicious/noExplicitAny: Because I can */
4
+ import * as fs from 'node:fs/promises';
5
+ import * as path from 'node:path';
6
+ import axios from 'axios';
7
+ import type { IProperty } from '../interfaces/IProperty.js';
8
+ import { testwiseConfig } from '../services/ConfigBuilder.js';
9
+ import { DataRetriever } from './helpers/DataRetriever.js';
10
+
11
+ type Subjects = {
12
+ subject: string;
13
+ variant: string;
14
+ screentype_context: string;
15
+ screentype_id: 'main' | 'detail' | 'zoom' | 'popup';
16
+ properties?: IProperty[];
17
+ };
18
+
19
+ const serviceUrl: string = testwiseConfig().get<string>('environmentSettings.serviceUrl')!.replace(/\/$/, '');
20
+ const metaEndpoint: string = testwiseConfig()
21
+ .get<string>('environmentSettings.metaEndpoint')!
22
+ .replace(/^\//, '')
23
+ .replace(/\/$/, '');
24
+ const authUser: string = testwiseConfig().get<string>('environmentSettings.authUser')!;
25
+ const authUserPassword: string = testwiseConfig().get<string>('environmentSettings.authUserPassword')!;
26
+
27
+ const axiosInstance = axios.create({
28
+ baseURL: serviceUrl,
29
+ headers: {
30
+ Authorization: `Basic ${Buffer.from(`${authUser}:${authUserPassword}`).toString('base64')}`,
31
+ accept: '*/*'
32
+ }
33
+ });
34
+
35
+ const guiApplId: number = await getGuiApplId();
36
+
37
+ export async function buildSubjects() {
38
+ const response = await axiosInstance.get(`/iam/${metaEndpoint}/i_ui_tab?$filter=gui_appl_id%20eq%20${guiApplId}`);
39
+
40
+ const rawTabs: any[] = response.data?.value || [];
41
+
42
+ const subjects: Subjects[] = rawTabs
43
+ .flatMap((tab: any) => {
44
+ const base = {
45
+ subject: tab.tab_id,
46
+ variant: tab.tab_variant_id
47
+ };
48
+
49
+ return [
50
+ { ...base, screentype_id: tab.main_screen_type_id, screentype_context: 'main' },
51
+ { ...base, screentype_id: tab.detail_screen_type_id, screentype_context: 'detail' },
52
+ { ...base, screentype_id: tab.zoom_screen_type_id, screentype_context: 'zoom' },
53
+ { ...base, screentype_id: tab.popup_screen_type_id, screentype_context: 'popup' }
54
+ ];
55
+ })
56
+ .filter((item) => item.screentype_id !== 'not_visible_in_gui');
57
+
58
+ const colResponse = await axiosInstance.get(`/iam/${metaEndpoint}/i_ui_col?$filter=gui_appl_id%20eq%20${guiApplId}`);
59
+
60
+ const allProperties: IPropertyWithSubjectIdentifiers[] = colResponse.data?.value.map((col: any) => ({
61
+ subject: col.tab_id,
62
+ variant: col.tab_variant_id,
63
+ col: col.col_id,
64
+ control_type: col.control_id || ''
65
+ }));
66
+
67
+ for (const subject of subjects) {
68
+ const matchingProperties = allProperties.filter(
69
+ (prop) => prop.subject === subject.subject && prop.variant === subject.variant
70
+ );
71
+
72
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
73
+ subject.properties = matchingProperties.map(({ subject, variant, ...rest }) => rest);
74
+ }
75
+
76
+ const subjectsWithLookupsSet = await setLookupDropdowns(subjects);
77
+
78
+ const seedDataDirectory = new DataRetriever().getSeedDataDirectory();
79
+
80
+ const seedDataPath = path.join(seedDataDirectory, 'subjects.json');
81
+ await fs.writeFile(seedDataPath, JSON.stringify(subjectsWithLookupsSet, null, 2), 'utf-8');
82
+ console.log(`Seed data written to ${seedDataPath}`);
83
+ }
84
+
85
+ async function setLookupDropdowns(subjects: Subjects[]): Promise<Subjects[]> {
86
+ const response = await axiosInstance.get(
87
+ `/iam/${metaEndpoint}/i_ui_tab_look_up?$filter=gui_appl_id%20eq%20${guiApplId}`
88
+ );
89
+
90
+ const lookupMappings: any[] = response.data?.value || [];
91
+
92
+ for (const mapping of lookupMappings) {
93
+ const targetSubject = subjects.find(
94
+ (subj) => subj.subject === mapping.tab_id && subj.variant === mapping.tab_variant_id
95
+ );
96
+
97
+ const targetProperty = targetSubject?.properties?.find((prop) => prop.col === mapping.col_id);
98
+
99
+ if (targetProperty) {
100
+ targetProperty.control_type = 'LOOKUP_DROPDOWN';
101
+ }
102
+ }
103
+
104
+ return subjects;
105
+ }
106
+
107
+ export async function buildScreens() {
108
+ const response = await axiosInstance.get(
109
+ `/iam/${metaEndpoint}/i_ui_screen_component?$filter=gui_appl_id eq ${guiApplId}`
110
+ );
111
+
112
+ const rawComponents = response.data?.value || [];
113
+ const screenMap = new Map<string, any>();
114
+
115
+ for (const item of rawComponents) {
116
+ // eslint-disable-next-line @typescript-eslint/naming-convention
117
+ const { screen_type_id, screen_component_id, screen_component_type_id } = item;
118
+
119
+ if (screen_component_id.includes(':')) continue;
120
+
121
+ if (!screenMap.has(screen_type_id)) {
122
+ screenMap.set(screen_type_id, { ScreenTypeId: screen_type_id, Components: [] });
123
+ }
124
+
125
+ screenMap.get(screen_type_id).Components.push({
126
+ ScreenComponentId: screen_component_id,
127
+ ScreenComponentType: screen_component_type_id
128
+ });
129
+ }
130
+
131
+ const seedDataDirectory = new DataRetriever().getSeedDataDirectory();
132
+ const outputDir = path.join(seedDataDirectory, 'screen-schemas');
133
+ await fs.mkdir(outputDir, { recursive: true });
134
+
135
+ for (const [screenTypeId, screenData] of screenMap.entries()) {
136
+ const usedTypes = new Set<string>();
137
+ const properties: Record<string, any> = {};
138
+ const required: string[] = [];
139
+
140
+ for (const comp of screenData.Components) {
141
+ const type = comp.ScreenComponentType;
142
+ properties[comp.ScreenComponentId] = { $ref: `#/definitions/${type}` };
143
+ required.push(comp.ScreenComponentId);
144
+ usedTypes.add(type);
145
+ }
146
+
147
+ const schema = {
148
+ title: `I_${screenTypeId}`,
149
+ type: 'object',
150
+ properties,
151
+ required,
152
+ additionalProperties: false,
153
+ definitions: Array.from(usedTypes).reduce((acc, type) => {
154
+ acc[type] = {
155
+ type: 'object',
156
+ properties: { temp: { type: 'string' } },
157
+ required: ['temp'],
158
+ additionalProperties: false
159
+ };
160
+ return acc;
161
+ }, {} as any)
162
+ };
163
+
164
+ await fs.writeFile(path.join(outputDir, `${screenTypeId}.json`), JSON.stringify(schema, null, 2));
165
+ }
166
+ console.log('Screen schemas generated successfully.');
167
+ }
168
+
169
+ async function getGuiApplId(): Promise<number> {
170
+ const guiApplAlias = testwiseConfig().get<string>('environmentSettings.guiApplAlias');
171
+
172
+ if (guiApplAlias) {
173
+ const response = await axiosInstance.get(`/iam/${metaEndpoint}/i_ui_gui_appl`);
174
+ const guiAppl = response.data?.value.find((appl: any) => {
175
+ const currentAlias = appl?.gui_appl_alias?.toString();
176
+ const targetAlias = guiApplAlias?.toString();
177
+
178
+ return currentAlias === targetAlias;
179
+ });
180
+
181
+ if (guiAppl) {
182
+ return guiAppl.gui_appl_id;
183
+ } else {
184
+ throw new Error(`No GUI Application found for GUI Application Alias: ${guiApplAlias}`);
185
+ }
186
+ } else {
187
+ throw new Error('GUI Application Alias is not defined in the configuration.');
188
+ }
189
+ }
190
+
191
+ interface IPropertyWithSubjectIdentifiers extends IProperty {
192
+ subject: string;
193
+ variant: string;
194
+ }
@@ -0,0 +1,39 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { DataRetriever } from '../index.js';
4
+ import type { IRegisteredSubjects } from '../interfaces/IRegisteredSubjects.js';
5
+
6
+ export class ModelDataRefiner {
7
+ private _dataRetriever = new DataRetriever();
8
+
9
+ public run() {
10
+ const seedDataDir = this._dataRetriever.getSeedDataDirectory();
11
+ const registeredSubjects: IRegisteredSubjects[] | null = this._dataRetriever.getRegisteredSubjects();
12
+
13
+ if (!registeredSubjects || registeredSubjects.length === 0) {
14
+ console.info('No registered subjects found.');
15
+ return;
16
+ }
17
+
18
+ const subjects = this._dataRetriever.getSubjectsSeedData();
19
+
20
+ const subjectsToBuild = subjects.filter((subject) =>
21
+ registeredSubjects.some(
22
+ (registered) =>
23
+ registered.subject === subject.subject &&
24
+ (registered.variant != null ? registered.variant === subject.variant : subject.variant === '') &&
25
+ registered.screen_type === subject.screentype_context
26
+ )
27
+ );
28
+
29
+ const subjectsToBuildPath = path.resolve(seedDataDir, 'subjectsToBuild.json');
30
+ fs.writeFileSync(subjectsToBuildPath, JSON.stringify(subjectsToBuild, null, 2));
31
+ console.log(`Created subjectsToBuild.json with ${subjectsToBuild.length} subjects.`);
32
+
33
+ const screensToBuild = Array.from(new Set(subjectsToBuild.map((subject) => subject.screentype_id)));
34
+
35
+ const screensToBuildPath = path.resolve(seedDataDir, 'screensToBuild.json');
36
+ fs.writeFileSync(screensToBuildPath, JSON.stringify(screensToBuild, null, 2));
37
+ console.log(`Created screensToBuild.json with ${screensToBuild.length} screens.`);
38
+ }
39
+ }
@@ -0,0 +1,131 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { DataRetriever } from '../index.js';
7
+ import type { ISubject } from '../interfaces/ISubject.js';
8
+ import type { SubjectComponents } from '../types/Components.js';
9
+ import { NamingHandler } from './helpers/NamingHandler.js';
10
+
11
+ const currentFilename = fileURLToPath(import.meta.url);
12
+ const currentDirname = dirname(currentFilename);
13
+
14
+ export class SchemaGenerator {
15
+ private _namingHandler: NamingHandler;
16
+ private _dataRetriever: DataRetriever;
17
+
18
+ constructor() {
19
+ this._namingHandler = new NamingHandler();
20
+ this._dataRetriever = new DataRetriever();
21
+ }
22
+
23
+ public generateSchema() {
24
+ const schemasDir = path.resolve(currentDirname, '../dist/test-artifacts/schemas');
25
+
26
+ if (!fs.existsSync(schemasDir)) {
27
+ fs.mkdirSync(schemasDir, { recursive: true });
28
+ }
29
+
30
+ const subjects = this._dataRetriever.getSubjectsToBuild();
31
+
32
+ for (const subject of subjects) {
33
+ this.createSubjectSchema(subject);
34
+ }
35
+ }
36
+
37
+ public iSubjectToJson(value: ISubject): string {
38
+ return JSON.stringify(value);
39
+ }
40
+
41
+ // Changes start here
42
+ private createSubjectSchema(subject: ISubject): void {
43
+ const screen = { content: this.getScreenContent(subject.screentype_id) };
44
+ const componentsToProcess = this.getComponentsToProcess(screen);
45
+
46
+ for (const component of componentsToProcess) {
47
+ switch (component) {
48
+ case 'Form':
49
+ this.createComponentSchema(subject, 'Form');
50
+ break;
51
+ case 'Grid':
52
+ this.createComponentSchema(subject, 'Grid');
53
+ break;
54
+ }
55
+ }
56
+ }
57
+
58
+ private getComponentsToProcess(screen: { content: string }): string[] {
59
+ const componentsToProcess: string[] = [];
60
+ const subjectBasedComponents: string[] = ['Form', 'Grid'];
61
+
62
+ const importRegex = /import type \{([\s\S]*?)\} from ['"]\.\.\/\.\.\/components\/index\.js['"];?/;
63
+ const importMatches = importRegex.exec(screen.content);
64
+
65
+ if (importMatches?.[1]) {
66
+ const importedComponents = importMatches[1].split(',').map((component) => component.trim());
67
+ importedComponents.forEach((component) => {
68
+ if (subjectBasedComponents.includes(component)) {
69
+ componentsToProcess.push(component);
70
+ }
71
+ });
72
+ }
73
+
74
+ return componentsToProcess;
75
+ }
76
+
77
+ private getScreenContent(screenTypeId: string): string {
78
+ const screenFileName = this._namingHandler.formatPascalCaseName(screenTypeId);
79
+ const _filename = fileURLToPath(import.meta.url);
80
+ const _dirname = path.dirname(_filename);
81
+ const screenLocation = path.resolve(_dirname, '..', '..', 'test-artifacts/screens/');
82
+
83
+ return fs.readFileSync(path.join(screenLocation, `I${screenFileName}.ts`), 'utf-8');
84
+ }
85
+
86
+ private createComponentSchema(subject: ISubject, componentType: SubjectComponents): void {
87
+ const interfaceName = `${subject.subject}_${componentType.toLowerCase()}`;
88
+ const schemasDir = path.join(currentDirname, '..', '..', 'dist/test-artifacts/schemas');
89
+ const fileName = `${this._namingHandler.snakeToPascalCase(interfaceName)}.json`;
90
+
91
+ if (fs.existsSync(path.join(schemasDir, fileName))) {
92
+ return;
93
+ }
94
+
95
+ // biome-ignore lint/suspicious/noExplicitAny: reason 42
96
+ const componentSchema: any = {
97
+ title: interfaceName,
98
+ type: 'object',
99
+ properties: {},
100
+ required: [],
101
+ additionalProperties: false,
102
+ definitions: {
103
+ Locator: {
104
+ type: 'object',
105
+ properties: {
106
+ selector: { type: 'string' }
107
+ },
108
+ required: ['selector'],
109
+ additionalProperties: false
110
+ }
111
+ }
112
+ };
113
+
114
+ subject.properties.forEach((property) => {
115
+ const componentTypeSuffix = this._namingHandler.getElementSuffixFromControlTypeAndComponentType(
116
+ property.control_type,
117
+ componentType
118
+ );
119
+ const fieldName = `${property.col}_${componentTypeSuffix.toLocaleLowerCase()}`;
120
+
121
+ componentSchema.properties[fieldName] = { $ref: '#/definitions/Locator' };
122
+ componentSchema.required.push(fieldName);
123
+ });
124
+
125
+ if (!fs.existsSync(schemasDir)) {
126
+ fs.mkdirSync(schemasDir, { recursive: true });
127
+ }
128
+
129
+ fs.writeFileSync(path.join(schemasDir, fileName), JSON.stringify(componentSchema, null, 2));
130
+ }
131
+ }