@thinkwise/testwise 0.1.97 → 0.2.0-beta.0

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