@thinkwise/testwise 0.2.103 → 0.2.111

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 (103) hide show
  1. package/artifact-builder/CustomActionBarGenerator.ts +210 -0
  2. package/artifact-builder/InterfaceGenerator.ts +4 -1
  3. package/artifact-builder/ScreenInterfaceRefiner.ts +7 -1
  4. package/artifact-builder/SubjectComponentGenerator.ts +7 -1
  5. package/artifact-builder/SubjectRegistration.ts +4 -1
  6. package/artifact-builder/TaskDataBuilder.ts +71 -0
  7. package/artifact-builder/helpers/DataRetriever.ts +11 -0
  8. package/artifact-builder/helpers/PipelinePaths.ts +5 -0
  9. package/artifact-builder/index.ts +2 -0
  10. package/components/action-bar/ActionBarObjects.ts +3 -1
  11. package/components/filter/FilterPopup.ts +77 -14
  12. package/components/filter/FilterPopupObjects.ts +25 -4
  13. package/components/grid/Grid.ts +40 -2
  14. package/components/grid/GridObjects.ts +18 -3
  15. package/components/pop-up/AllRowsPopUp.ts +4 -1
  16. package/controls/DateControl.ts +26 -13
  17. package/controls/DateControlObjects.ts +1 -1
  18. package/controls/FormComboBoxObjects.ts +2 -2
  19. package/controls/GridColumnControl.ts +10 -0
  20. package/dist/artifact-builder/CustomActionBarGenerator.d.ts +82 -0
  21. package/dist/artifact-builder/CustomActionBarGenerator.js +175 -0
  22. package/dist/artifact-builder/CustomActionBarGenerator.js.map +1 -0
  23. package/dist/artifact-builder/InterfaceGenerator.js +4 -1
  24. package/dist/artifact-builder/InterfaceGenerator.js.map +1 -1
  25. package/dist/artifact-builder/ScreenInterfaceRefiner.js +7 -1
  26. package/dist/artifact-builder/ScreenInterfaceRefiner.js.map +1 -1
  27. package/dist/artifact-builder/SubjectComponentGenerator.js +5 -1
  28. package/dist/artifact-builder/SubjectComponentGenerator.js.map +1 -1
  29. package/dist/artifact-builder/SubjectRegistration.js +4 -1
  30. package/dist/artifact-builder/SubjectRegistration.js.map +1 -1
  31. package/dist/artifact-builder/TaskDataBuilder.d.ts +21 -0
  32. package/dist/artifact-builder/TaskDataBuilder.js +60 -0
  33. package/dist/artifact-builder/TaskDataBuilder.js.map +1 -0
  34. package/dist/artifact-builder/helpers/DataRetriever.d.ts +2 -0
  35. package/dist/artifact-builder/helpers/DataRetriever.js +7 -0
  36. package/dist/artifact-builder/helpers/DataRetriever.js.map +1 -1
  37. package/dist/artifact-builder/helpers/PipelinePaths.d.ts +2 -0
  38. package/dist/artifact-builder/helpers/PipelinePaths.js +4 -0
  39. package/dist/artifact-builder/helpers/PipelinePaths.js.map +1 -1
  40. package/dist/artifact-builder/index.d.ts +2 -0
  41. package/dist/artifact-builder/index.js +2 -0
  42. package/dist/artifact-builder/index.js.map +1 -1
  43. package/dist/components/action-bar/ActionBarObjects.js +2 -1
  44. package/dist/components/action-bar/ActionBarObjects.js.map +1 -1
  45. package/dist/components/filter/FilterPopup.d.ts +50 -2
  46. package/dist/components/filter/FilterPopup.js +69 -15
  47. package/dist/components/filter/FilterPopup.js.map +1 -1
  48. package/dist/components/filter/FilterPopupObjects.d.ts +4 -2
  49. package/dist/components/filter/FilterPopupObjects.js +18 -4
  50. package/dist/components/filter/FilterPopupObjects.js.map +1 -1
  51. package/dist/components/grid/Grid.d.ts +22 -0
  52. package/dist/components/grid/Grid.js +36 -2
  53. package/dist/components/grid/Grid.js.map +1 -1
  54. package/dist/components/grid/GridObjects.d.ts +2 -0
  55. package/dist/components/grid/GridObjects.js +12 -2
  56. package/dist/components/grid/GridObjects.js.map +1 -1
  57. package/dist/components/pop-up/AllRowsPopUp.js +4 -1
  58. package/dist/components/pop-up/AllRowsPopUp.js.map +1 -1
  59. package/dist/controls/DateControl.d.ts +18 -10
  60. package/dist/controls/DateControl.js +21 -8
  61. package/dist/controls/DateControl.js.map +1 -1
  62. package/dist/controls/DateControlObjects.js +1 -1
  63. package/dist/controls/DateControlObjects.js.map +1 -1
  64. package/dist/controls/FormComboBoxObjects.js +2 -2
  65. package/dist/controls/FormComboBoxObjects.js.map +1 -1
  66. package/dist/controls/GridColumnControl.d.ts +2 -0
  67. package/dist/controls/GridColumnControl.js +6 -0
  68. package/dist/controls/GridColumnControl.js.map +1 -1
  69. package/dist/helpers/DateFormat.d.ts +69 -0
  70. package/dist/helpers/DateFormat.js +100 -0
  71. package/dist/helpers/DateFormat.js.map +1 -0
  72. package/dist/helpers/DateLocaleHelper.d.ts +17 -0
  73. package/dist/helpers/DateLocaleHelper.js +52 -0
  74. package/dist/helpers/DateLocaleHelper.js.map +1 -0
  75. package/dist/helpers/LoginHelper.js +2 -1
  76. package/dist/helpers/LoginHelper.js.map +1 -1
  77. package/dist/helpers/Poll.d.ts +24 -2
  78. package/dist/helpers/Poll.js +87 -5
  79. package/dist/helpers/Poll.js.map +1 -1
  80. package/dist/helpers/UserSimulationHelper.js +2 -1
  81. package/dist/helpers/UserSimulationHelper.js.map +1 -1
  82. package/dist/interfaces/ITabTask.d.ts +21 -0
  83. package/dist/interfaces/ITabTask.js +2 -0
  84. package/dist/interfaces/ITabTask.js.map +1 -0
  85. package/dist/page-overrides/FillOverride.js +0 -15
  86. package/dist/page-overrides/FillOverride.js.map +1 -1
  87. package/dist/services/IndiciumApi.service.d.ts +7 -0
  88. package/dist/services/IndiciumApi.service.js +19 -0
  89. package/dist/services/IndiciumApi.service.js.map +1 -1
  90. package/dist/types/PollTypes.d.ts +5 -0
  91. package/helpers/DateFormat.ts +123 -0
  92. package/helpers/DateLocaleHelper.ts +57 -0
  93. package/helpers/LoginHelper.ts +2 -1
  94. package/helpers/Poll.ts +108 -5
  95. package/helpers/UserSimulationHelper.ts +2 -1
  96. package/interfaces/ITabTask.ts +21 -0
  97. package/package.json +4 -2
  98. package/page-overrides/FillOverride.ts +0 -18
  99. package/promptCredentials.js +124 -124
  100. package/scripts/checkPackageContents.js +39 -0
  101. package/scripts/sync.js +14 -0
  102. package/services/IndiciumApi.service.ts +21 -0
  103. package/types/PollTypes.ts +5 -0
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Represents a single subject+variant combination that has qualifying task
3
+ * buttons, as stored in `seed-data/tab-tasks.json` after the
4
+ * `TaskDataBuilder` pipeline step.
5
+ *
6
+ * Tasks with `custom_display_type === 4` are stripped at fetch time and never
7
+ * appear here. If all tasks for a subject+variant had that type, the entry is
8
+ * omitted entirely and that subject receives a plain `ActionBar`.
9
+ */
10
+ export interface ITabTask {
11
+ /** Thinkwise subject identifier (matches `ISubject.subject`). */
12
+ subject: string;
13
+ /** Subject variant identifier (matches `ISubject.variant`). */
14
+ variant: string;
15
+ /**
16
+ * Ordered list of qualifying task IDs.
17
+ * Each entry becomes a named getter on the generated custom ActionBar class.
18
+ * e.g. `"supplier-item-add"` → `get supplierItemAddButton(): Locator`
19
+ */
20
+ tasks: string[];
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkwise/testwise",
3
- "version": "0.2.103",
3
+ "version": "0.2.111",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,8 @@
11
11
  "lint": "eslint ./ --cache --ignore-pattern .gitignore",
12
12
  "format:fix": "run-s \"lint --fix\" && biome check --fix --unsafe",
13
13
  "format:check": "run-s \"lint \" && biome check",
14
+ "build": "tsc",
15
+ "prepack": "rimraf dist && tsc && node scripts/checkPackageContents.js",
14
16
  "postinstall": "node scripts/main.js postinstall",
15
17
  "test": "vitest run",
16
18
  "test:watch": "vitest",
@@ -22,7 +24,7 @@
22
24
  "license": "ISC",
23
25
  "description": "",
24
26
  "dependencies": {
25
- "@thinkwise/backups": "^1.0.20",
27
+ "@thinkwise/backups": "^1.0.19",
26
28
  "axios": "^1.9.0",
27
29
  "chalk": "^5.4.1",
28
30
  "change-case": "^5.4.4",
@@ -71,26 +71,8 @@ export class FillOverride {
71
71
  locator.fill = async (value, options) => {
72
72
  if (waitForRequestsToComplete) await this.flightTracker.waitToContinue();
73
73
 
74
- const [valueIsDifferent, isQualifyingFormField] = await Promise.all([
75
- locator.inputValue().then((v) => v !== value),
76
- locator.evaluate((el) => {
77
- const testId = el.getAttribute('data-testid');
78
- return (
79
- !!testId && testId.includes('form-field__') && el.closest('[data-testid^="form-section__"]') !== null
80
- );
81
- })
82
- ]);
83
-
84
74
  await page.waitEventHandler.waitForCanContinue();
85
-
86
75
  await originalFill(value, options);
87
-
88
- if (isQualifyingFormField) {
89
- if (valueIsDifferent) page.waitEventHandler.waitForResponse('/staged');
90
- await locator.evaluate((el) => {
91
- if (!el.getAttribute('aria-expanded')) el.blur();
92
- });
93
- }
94
76
  };
95
77
 
96
78
  for (const method of locatorMethods) {
@@ -1,124 +1,124 @@
1
- #!/usr/bin/env node
2
- import { spawnSync } from 'node:child_process';
3
- import { readFileSync } from 'node:fs';
4
- import process from 'node:process';
5
- import chalk from 'chalk';
6
- import prompts from 'prompts';
7
-
8
- const packageScripts = JSON.parse(readFileSync('./package.json')).scripts;
9
-
10
- function exitWithError(error) {
11
- console.log(chalk.red(error));
12
- process.exit(1);
13
- }
14
-
15
- function startTestSolution() {
16
- console.log('Now starting test solution...');
17
- spawnSync(`yarn --cwd ../ ${args.join(' ')}`, {
18
- shell: true,
19
- stdio: 'inherit'
20
- });
21
- process.exit();
22
- }
23
-
24
- const args = process.argv.slice(2);
25
-
26
- if (!args.length) {
27
- exitWithError('Error: Got too few parameters. Usage: yarn test:credentials test');
28
- }
29
-
30
- const yarnScriptToExec = args[0];
31
-
32
- if (!packageScripts[yarnScriptToExec]) {
33
- exitWithError(`Error: Given script "${yarnScriptToExec}" doesn't exist in package.json.`);
34
- }
35
-
36
- // INCOMING HACK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
37
- // temp fix to be reworked when converting this file to TS
38
- import fs from 'node:fs';
39
- import path from 'node:path';
40
-
41
- const indiciumURL = (() => {
42
- try {
43
- const configPath = path.resolve(process.cwd(), './Testwise.json');
44
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
45
- return config.environmentSettings?.serviceUrl || '';
46
- } catch (error) {
47
- console.error('Error reading Testwise.json:', error);
48
- return '';
49
- }
50
- })();
51
-
52
- // END OF HACK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
53
-
54
- if (process.env.SF_TEST_USERNAME && process.env.SF_TEST_PASSWORD) {
55
- console.log('Using SF_TEST_USERNAME and SF_TEST_PASSWORD environment variables.');
56
- startTestSolution();
57
- }
58
-
59
- const questions = [
60
- {
61
- type: 'text',
62
- name: 'username',
63
- message: 'Username:',
64
- validate: (value) => {
65
- if (!value?.length) {
66
- return 'Username is required';
67
- }
68
-
69
- if (value.toLowerCase() === 'demo') {
70
- return 'Running tests locally with demo is forbidden as it results in dataset concurrency problems';
71
- }
72
-
73
- return true;
74
- }
75
- },
76
- {
77
- type: 'invisible',
78
- name: 'password',
79
- message: 'Password:',
80
- validate: (value) => (!value?.length ? 'Password is required' : true)
81
- }
82
- ];
83
-
84
- (async () => {
85
- const credentialsResponse = await prompts(questions);
86
-
87
- if (!credentialsResponse?.username || !credentialsResponse.password) {
88
- exitWithError('Username or password was not given');
89
- }
90
-
91
- const { password, username } = credentialsResponse;
92
- const params = new URLSearchParams();
93
-
94
- params.append('UserName', username);
95
- params.append('Password', password);
96
-
97
- const fetch = (await import('node-fetch')).default;
98
- const authResponse = await fetch(`${indiciumURL}/account/api/login`, {
99
- method: 'POST',
100
- body: params
101
- });
102
-
103
- if (authResponse.status === 204) {
104
- console.log('Confirmed credentials work @', indiciumURL);
105
- } else {
106
- const confirmResponse = await prompts([
107
- {
108
- type: 'confirm',
109
- name: 'value',
110
- message: `Can't login to ${indiciumURL} with the given credentials. Continue anyways?`,
111
- initial: true
112
- }
113
- ]);
114
-
115
- if (!confirmResponse.value) {
116
- return;
117
- }
118
- }
119
-
120
- process.env.SF_TEST_USERNAME = username;
121
- process.env.SF_TEST_PASSWORD = password;
122
-
123
- startTestSolution();
124
- })();
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import { readFileSync } from 'node:fs';
4
+ import process from 'node:process';
5
+ import chalk from 'chalk';
6
+ import prompts from 'prompts';
7
+
8
+ const packageScripts = JSON.parse(readFileSync('./package.json')).scripts;
9
+
10
+ function exitWithError(error) {
11
+ console.log(chalk.red(error));
12
+ process.exit(1);
13
+ }
14
+
15
+ function startTestSolution() {
16
+ console.log('Now starting test solution...');
17
+ spawnSync(`yarn --cwd ../ ${args.join(' ')}`, {
18
+ shell: true,
19
+ stdio: 'inherit'
20
+ });
21
+ process.exit();
22
+ }
23
+
24
+ const args = process.argv.slice(2);
25
+
26
+ if (!args.length) {
27
+ exitWithError('Error: Got too few parameters. Usage: yarn test:credentials test');
28
+ }
29
+
30
+ const yarnScriptToExec = args[0];
31
+
32
+ if (!packageScripts[yarnScriptToExec]) {
33
+ exitWithError(`Error: Given script "${yarnScriptToExec}" doesn't exist in package.json.`);
34
+ }
35
+
36
+ // INCOMING HACK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
37
+ // temp fix to be reworked when converting this file to TS
38
+ import fs from 'node:fs';
39
+ import path from 'node:path';
40
+
41
+ const indiciumURL = (() => {
42
+ try {
43
+ const configPath = path.resolve(process.cwd(), './Testwise.json');
44
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
45
+ return config.environmentSettings?.serviceUrl || '';
46
+ } catch (error) {
47
+ console.error('Error reading Testwise.json:', error);
48
+ return '';
49
+ }
50
+ })();
51
+
52
+ // END OF HACK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
53
+
54
+ if (process.env.SF_TEST_USERNAME && process.env.SF_TEST_PASSWORD) {
55
+ console.log('Using SF_TEST_USERNAME and SF_TEST_PASSWORD environment variables.');
56
+ startTestSolution();
57
+ }
58
+
59
+ const questions = [
60
+ {
61
+ type: 'text',
62
+ name: 'username',
63
+ message: 'Username:',
64
+ validate: (value) => {
65
+ if (!value?.length) {
66
+ return 'Username is required';
67
+ }
68
+
69
+ if (value.toLowerCase() === 'demo') {
70
+ return 'Running tests locally with demo is forbidden as it results in dataset concurrency problems';
71
+ }
72
+
73
+ return true;
74
+ }
75
+ },
76
+ {
77
+ type: 'invisible',
78
+ name: 'password',
79
+ message: 'Password:',
80
+ validate: (value) => (!value?.length ? 'Password is required' : true)
81
+ }
82
+ ];
83
+
84
+ (async () => {
85
+ const credentialsResponse = await prompts(questions);
86
+
87
+ if (!credentialsResponse?.username || !credentialsResponse.password) {
88
+ exitWithError('Username or password was not given');
89
+ }
90
+
91
+ const { password, username } = credentialsResponse;
92
+ const params = new URLSearchParams();
93
+
94
+ params.append('UserName', username);
95
+ params.append('Password', password);
96
+
97
+ const fetch = (await import('node-fetch')).default;
98
+ const authResponse = await fetch(`${indiciumURL}/account/api/login`, {
99
+ method: 'POST',
100
+ body: params
101
+ });
102
+
103
+ if (authResponse.status === 204) {
104
+ console.log('Confirmed credentials work @', indiciumURL);
105
+ } else {
106
+ const confirmResponse = await prompts([
107
+ {
108
+ type: 'confirm',
109
+ name: 'value',
110
+ message: `Can't login to ${indiciumURL} with the given credentials. Continue anyways?`,
111
+ initial: true
112
+ }
113
+ ]);
114
+
115
+ if (!confirmResponse.value) {
116
+ return;
117
+ }
118
+ }
119
+
120
+ process.env.SF_TEST_USERNAME = username;
121
+ process.env.SF_TEST_PASSWORD = password;
122
+
123
+ startTestSolution();
124
+ })();
@@ -0,0 +1,39 @@
1
+ import { execSync } from 'node:child_process';
2
+ import process from 'node:process';
3
+
4
+ /**
5
+ * Refuses to pack when generated model files would be published.
6
+ *
7
+ * `testwise sync` writes subject classes, screen interfaces, schemas and seed data
8
+ * for whichever application it last ran against, and `package.json` `files`
9
+ * publishes all of `dist/`. This runs at the end of `prepack`, after `dist/` was
10
+ * emptied and rebuilt, and fails the pack when anything under `seed-data/` or
11
+ * `test-artifacts/` does not come from a file tracked in git.
12
+ */
13
+
14
+ const packedFiles = JSON.parse(
15
+ execSync('npm pack --dry-run --json --ignore-scripts', { encoding: 'utf8' })
16
+ )[0].files.map((file) => file.path);
17
+
18
+ const trackedSources = new Set(
19
+ execSync('git ls-files test-artifacts', { encoding: 'utf8' })
20
+ .split(/\r?\n/)
21
+ .filter(Boolean)
22
+ .map((file) => file.replace(/\.ts$/, ''))
23
+ );
24
+
25
+ const toSource = (file) => file.replace(/^dist\//, '').replace(/\.(d\.ts\.map|d\.ts|js\.map|js|ts)$/, '');
26
+
27
+ const generatedFiles = packedFiles.filter(
28
+ (file) =>
29
+ file.includes('seed-data/') || (/^(dist\/)?test-artifacts\//.test(file) && !trackedSources.has(toSource(file)))
30
+ );
31
+
32
+ if (generatedFiles.length > 0) {
33
+ console.error(
34
+ `Refusing to pack: ${generatedFiles.length} generated model file(s) would be published, for example ${generatedFiles.slice(0, 5).join(', ')}. Delete dist/ and build again.`
35
+ );
36
+ process.exit(1);
37
+ }
38
+
39
+ console.log(`Package contents checked: ${packedFiles.length} files, no generated model files.`);
package/scripts/sync.js CHANGED
@@ -391,6 +391,7 @@ async function phaseBuildModelData(ctx) {
391
391
  Logger.phase(2, PHASE_NAMES.BUILD);
392
392
 
393
393
  const { buildSubjects, buildScreens } = await import('../dist/artifact-builder/ModelDataBuilder.js');
394
+ const { buildTaskData } = await import('../dist/artifact-builder/TaskDataBuilder.js');
394
395
 
395
396
  const subjectsResult = await runStep(ctx, 'Build subjects from model data', async () => {
396
397
  await buildSubjects();
@@ -407,6 +408,14 @@ async function phaseBuildModelData(ctx) {
407
408
  if (!screensResult.success) {
408
409
  throw screensResult.error;
409
410
  }
411
+
412
+ const taskDataResult = await runStep(ctx, 'Build task data for custom ActionBar generation', async () => {
413
+ await buildTaskData();
414
+ });
415
+
416
+ if (!taskDataResult.success) {
417
+ throw taskDataResult.error;
418
+ }
410
419
  }
411
420
 
412
421
  /**
@@ -447,6 +456,7 @@ async function phaseGenerateArtifacts(ctx) {
447
456
  SchemaGenerator,
448
457
  SubjectComponentGenerator,
449
458
  SubjectGenerator,
459
+ CustomActionBarGenerator,
450
460
  SubjectRegistration
451
461
  } = await import('../dist/artifact-builder/index.js');
452
462
 
@@ -478,6 +488,10 @@ async function phaseGenerateArtifacts(ctx) {
478
488
  name: 'Generate subject classes',
479
489
  fn: () => new SubjectGenerator().run()
480
490
  },
491
+ {
492
+ name: 'Generate custom ActionBar subclasses for subjects with tasks',
493
+ fn: () => new CustomActionBarGenerator().run()
494
+ },
481
495
  {
482
496
  name: 'Add subjects to registry',
483
497
  fn: async () => await new SubjectRegistration().run()
@@ -178,6 +178,27 @@ export class IndiciumApiService {
178
178
  }
179
179
  }
180
180
 
181
+ /**
182
+ * Fetches all task button records (`i_ui_tab_task`) for the verified GUI application.
183
+ * Results include all tasks regardless of `custom_display_type`; callers are
184
+ * responsible for filtering out `custom_display_type === 4` entries.
185
+ * @returns An array of tab task objects, or an empty array if not verified or on error.
186
+ */
187
+ public async getTabTasks(): Promise<any[]> {
188
+ await this.ensureReady();
189
+ if (!this.isVerified || !this.guiApplId) return [];
190
+
191
+ try {
192
+ const response = await this.axiosInstance!.get(
193
+ `/iam/${this.metaEndpoint}/i_ui_tab_task?$filter=gui_appl_id%20eq%20${this.guiApplId}`
194
+ );
195
+ return response.data?.value || [];
196
+ } catch (error) {
197
+ console.error(`IndiciumApiService: Failed to fetch tab tasks: ${IndiciumApiService.extractMessage(error)}`);
198
+ return [];
199
+ }
200
+ }
201
+
181
202
  /**
182
203
  * Returns the verified GUI Application ID.
183
204
  * Throws an error if verification failed or the alias was not found.
@@ -11,4 +11,9 @@ export type PollOptions<T = unknown> = {
11
11
  interval?: number; // ms (default 200)
12
12
  predicate?: (v: T) => boolean; // default truthy
13
13
  onTick?: (v: T, elapsedMs: number) => void;
14
+ /**
15
+ * What is being waited for, phrased to follow "waiting for ...". Included in the
16
+ * timeout message, so a failure reads as a sentence rather than a bare timeout.
17
+ */
18
+ description?: string;
14
19
  };