@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.
- package/Testwise.ts +8 -13
- package/artifact-builder/ArtifactManager.ts +34 -0
- package/artifact-builder/InterfaceGenerator.ts +183 -0
- package/artifact-builder/ModelDataBuilder.ts +177 -0
- package/artifact-builder/ModelDataRefiner.ts +38 -0
- package/artifact-builder/SchemaGenerator.ts +134 -0
- package/artifact-builder/ScreenInterfaceRefiner.ts +159 -0
- package/artifact-builder/SelectorBuilder.ts +82 -0
- package/artifact-builder/SubjectComponentGenerator.ts +186 -0
- package/artifact-builder/SubjectGenerator.ts +332 -0
- package/artifact-builder/SubjectRegistration.ts +136 -0
- package/artifact-builder/helpers/DataRetriever.ts +64 -0
- package/artifact-builder/helpers/NamingHandler.ts +180 -0
- package/artifact-builder/helpers/index.ts +2 -0
- package/artifact-builder/index.ts +9 -0
- package/components/{actionbar/Actionbar.ts → action-bar/ActionBar.ts} +12 -12
- package/components/{actionbar/ActionbarObjects.ts → action-bar/ActionBarObjects.ts} +4 -4
- package/components/{actionbar → action-bar}/CustomActionBar.ts +2 -2
- package/components/index.ts +3 -4
- package/components/tab/DetailTabPage.ts +20 -0
- package/components/tab/{DetailTabObjects.ts → DetailTabPageObjects.ts} +1 -1
- package/components/tab/Tab.ts +31 -5
- package/components/tab/TabObjects.ts +15 -3
- package/dist/Testwise.d.ts +0 -1
- package/dist/Testwise.js +7 -14
- package/dist/Testwise.js.map +1 -1
- package/dist/artifact-builder/ArtifactManager.d.ts +8 -0
- package/dist/artifact-builder/ArtifactManager.js +27 -0
- package/dist/artifact-builder/ArtifactManager.js.map +1 -0
- package/dist/artifact-builder/InterfaceGenerator.d.ts +16 -0
- package/dist/artifact-builder/InterfaceGenerator.js +134 -0
- package/dist/artifact-builder/InterfaceGenerator.js.map +1 -0
- package/dist/artifact-builder/ModelDataBuilder.d.ts +2 -0
- package/dist/artifact-builder/ModelDataBuilder.js +128 -0
- package/dist/artifact-builder/ModelDataBuilder.js.map +1 -0
- package/dist/artifact-builder/ModelDataRefiner.d.ts +4 -0
- package/dist/artifact-builder/ModelDataRefiner.js +28 -0
- package/dist/artifact-builder/ModelDataRefiner.js.map +1 -0
- package/dist/artifact-builder/SchemaGenerator.d.ts +12 -0
- package/dist/artifact-builder/SchemaGenerator.js +104 -0
- package/dist/artifact-builder/SchemaGenerator.js.map +1 -0
- package/dist/artifact-builder/ScreenInterfaceRefiner.d.ts +15 -0
- package/dist/artifact-builder/ScreenInterfaceRefiner.js +125 -0
- package/dist/artifact-builder/ScreenInterfaceRefiner.js.map +1 -0
- package/dist/artifact-builder/SelectorBuilder.d.ts +13 -0
- package/dist/artifact-builder/SelectorBuilder.js +69 -0
- package/dist/artifact-builder/SelectorBuilder.js.map +1 -0
- package/dist/artifact-builder/SubjectComponentGenerator.d.ts +23 -0
- package/dist/artifact-builder/SubjectComponentGenerator.js +136 -0
- package/dist/artifact-builder/SubjectComponentGenerator.js.map +1 -0
- package/dist/artifact-builder/SubjectGenerator.d.ts +27 -0
- package/dist/artifact-builder/SubjectGenerator.js +235 -0
- package/dist/artifact-builder/SubjectGenerator.js.map +1 -0
- package/dist/artifact-builder/SubjectRegistration.d.ts +22 -0
- package/dist/artifact-builder/SubjectRegistration.js +96 -0
- package/dist/artifact-builder/SubjectRegistration.js.map +1 -0
- package/dist/artifact-builder/helpers/DataRetriever.d.ts +12 -0
- package/dist/artifact-builder/helpers/DataRetriever.js +52 -0
- package/dist/artifact-builder/helpers/DataRetriever.js.map +1 -0
- package/dist/artifact-builder/helpers/NamingHandler.d.ts +24 -0
- package/dist/artifact-builder/helpers/NamingHandler.js +145 -0
- package/dist/artifact-builder/helpers/NamingHandler.js.map +1 -0
- package/dist/artifact-builder/helpers/index.d.ts +2 -0
- package/dist/artifact-builder/helpers/index.js +3 -0
- package/dist/artifact-builder/helpers/index.js.map +1 -0
- package/dist/artifact-builder/index.d.ts +9 -0
- package/dist/artifact-builder/index.js +10 -0
- package/dist/artifact-builder/index.js.map +1 -0
- package/dist/components/{actionbar/Actionbar.d.ts → action-bar/ActionBar.d.ts} +4 -4
- package/dist/components/{actionbar/Actionbar.js → action-bar/ActionBar.js} +9 -9
- package/dist/components/action-bar/ActionBar.js.map +1 -0
- package/dist/components/{actionbar/ActionbarObjects.d.ts → action-bar/ActionBarObjects.d.ts} +2 -2
- package/dist/components/{actionbar/ActionbarObjects.js → action-bar/ActionBarObjects.js} +5 -5
- package/dist/components/action-bar/ActionBarObjects.js.map +1 -0
- package/dist/components/{actionbar → action-bar}/CustomActionBar.d.ts +2 -2
- package/dist/components/action-bar/CustomActionBar.js +7 -0
- package/dist/components/action-bar/CustomActionBar.js.map +1 -0
- package/dist/components/index.d.ts +3 -4
- package/dist/components/index.js +3 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/tab/DetailTabPage.d.ts +9 -0
- package/dist/components/tab/DetailTabPage.js +15 -0
- package/dist/components/tab/DetailTabPage.js.map +1 -0
- package/dist/components/tab/{DetailTabObjects.d.ts → DetailTabPageObjects.d.ts} +1 -1
- package/dist/components/tab/{DetailTabObjects.js → DetailTabPageObjects.js} +2 -2
- package/dist/components/tab/DetailTabPageObjects.js.map +1 -0
- package/dist/components/tab/Tab.d.ts +8 -3
- package/dist/components/tab/Tab.js +25 -4
- package/dist/components/tab/Tab.js.map +1 -1
- package/dist/components/tab/TabObjects.d.ts +7 -2
- package/dist/components/tab/TabObjects.js +9 -2
- package/dist/components/tab/TabObjects.js.map +1 -1
- package/dist/enums/ElementTypes.d.ts +8 -0
- package/dist/enums/ElementTypes.js +10 -0
- package/dist/enums/ElementTypes.js.map +1 -0
- package/dist/helpers/PathResolver.d.ts +3 -0
- package/dist/helpers/PathResolver.js +26 -0
- package/dist/helpers/PathResolver.js.map +1 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/IProperty.d.ts +4 -0
- package/dist/interfaces/IProperty.js +2 -0
- package/dist/interfaces/IProperty.js.map +1 -0
- package/dist/interfaces/IRegisteredSubjects.d.ts +5 -0
- package/dist/interfaces/IRegisteredSubjects.js +2 -0
- package/dist/interfaces/IRegisteredSubjects.js.map +1 -0
- package/dist/interfaces/ISubject.d.ts +8 -0
- package/dist/interfaces/ISubject.js +2 -0
- package/dist/interfaces/ISubject.js.map +1 -0
- package/dist/page-extensions/SubjectProvider.d.ts +11 -0
- package/dist/page-extensions/SubjectProvider.js +24 -0
- package/dist/page-extensions/SubjectProvider.js.map +1 -0
- package/dist/page-extensions/SubjectRegistry.d.ts +14 -0
- package/dist/page-extensions/SubjectRegistry.js +14 -0
- package/dist/page-extensions/SubjectRegistry.js.map +1 -0
- package/dist/page-extensions/index.d.ts +3 -0
- package/dist/page-extensions/index.js +3 -0
- package/dist/page-extensions/index.js.map +1 -1
- package/dist/services/ConfigBuilder.d.ts +1 -0
- package/dist/services/ConfigBuilder.js +20 -1
- package/dist/services/ConfigBuilder.js.map +1 -1
- package/dist/test-artifacts/SubjectPageBase.d.ts +5 -0
- package/dist/test-artifacts/SubjectPageBase.js +6 -0
- package/dist/test-artifacts/SubjectPageBase.js.map +1 -0
- package/dist/test-artifacts/index.d.ts +3 -0
- package/dist/test-artifacts/index.js +4 -0
- package/dist/test-artifacts/index.js.map +1 -0
- package/dist/test-artifacts/screens/index.d.ts +1 -0
- package/dist/test-artifacts/screens/index.js +2 -0
- package/dist/test-artifacts/screens/index.js.map +1 -0
- package/dist/test-artifacts/subjects/index.d.ts +1 -0
- package/dist/test-artifacts/subjects/index.js +2 -0
- package/dist/test-artifacts/subjects/index.js.map +1 -0
- package/dist/types/Components.d.ts +7 -0
- package/dist/types/Components.js +28 -0
- package/dist/types/Components.js.map +1 -0
- package/enums/ElementTypes.ts +8 -0
- package/helpers/PathResolver.ts +30 -0
- package/helpers/index.ts +1 -0
- package/index.ts +1 -0
- package/interfaces/IProperty.ts +4 -0
- package/interfaces/IRegisteredSubjects.ts +5 -0
- package/interfaces/ISubject.ts +9 -0
- package/package.json +26 -9
- package/page-extensions/SubjectProvider.ts +41 -0
- package/page-extensions/SubjectRegistry.ts +30 -0
- package/page-extensions/index.ts +3 -0
- package/promptCredentials.js +124 -124
- package/scripts/Testwise.template.json +4 -1
- package/scripts/main.js +75 -4
- package/scripts/postinstall.js +42 -0
- package/scripts/setup.js +17 -14
- package/scripts/sync.js +69 -0
- package/scripts/tsconfig.template.json +1 -1
- package/services/ConfigBuilder.ts +25 -2
- package/test-artifacts/SubjectPageBase.ts +9 -0
- package/test-artifacts/index.ts +3 -0
- package/test-artifacts/screens/index.ts +0 -0
- package/test-artifacts/subjects/index.ts +0 -0
- package/tsconfig.json +1 -1
- package/types/Components.ts +55 -0
- package/components/tab/ComponentTab.ts +0 -40
- package/components/tab/ComponentTabObjects.ts +0 -17
- package/components/tab/DetailTab.ts +0 -20
- package/dist/Testwise.json +0 -25
- package/dist/bdd.d.ts +0 -6
- package/dist/bdd.js +0 -9
- package/dist/bdd.js.map +0 -1
- package/dist/biome.json +0 -52
- package/dist/components/actionbar/Actionbar.js.map +0 -1
- package/dist/components/actionbar/ActionbarObjects.js.map +0 -1
- package/dist/components/actionbar/CustomActionBar.js +0 -7
- package/dist/components/actionbar/CustomActionBar.js.map +0 -1
- package/dist/components/tab/ComponentTab.d.ts +0 -12
- package/dist/components/tab/ComponentTab.js +0 -31
- package/dist/components/tab/ComponentTab.js.map +0 -1
- package/dist/components/tab/ComponentTabObjects.d.ts +0 -8
- package/dist/components/tab/ComponentTabObjects.js +0 -11
- package/dist/components/tab/ComponentTabObjects.js.map +0 -1
- package/dist/components/tab/DetailTab.d.ts +0 -9
- package/dist/components/tab/DetailTab.js +0 -15
- package/dist/components/tab/DetailTab.js.map +0 -1
- package/dist/components/tab/DetailTabObjects.js.map +0 -1
- package/dist/helpers/TestExtensions.d.ts +0 -8
- package/dist/helpers/TestExtensions.js +0 -21
- package/dist/helpers/TestExtensions.js.map +0 -1
- package/dist/package-lock.json +0 -3852
- package/dist/package.json +0 -59
- package/dist/scripts/Testwise.template.json +0 -25
- package/dist/scripts/tsconfig.template.json +0 -12
- package/dist/services/ReportingService.d.ts +0 -8
- package/dist/services/ReportingService.js +0 -29
- package/dist/services/ReportingService.js.map +0 -1
- package/dist/tsconfig.json +0 -20
- 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
|
|
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
|
+
}
|