@thinkwise/testwise 0.0.2-alpha.2

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 (152) hide show
  1. package/.ci/azure-pipelines.yaml +80 -0
  2. package/.eslintcache +1 -0
  3. package/.gitattributes +3 -0
  4. package/.gitconfig +2 -0
  5. package/.vscode/settings.json +18 -0
  6. package/Testwise.ts +22 -0
  7. package/components/BaseComponent.ts +95 -0
  8. package/components/BaseComponentObjects.ts +10 -0
  9. package/components/IComponentObjects.ts +5 -0
  10. package/components/Splitter.ts +11 -0
  11. package/components/TSFComponent.ts +8 -0
  12. package/components/actionbar/Actionbar.ts +57 -0
  13. package/components/actionbar/ActionbarObjects.ts +67 -0
  14. package/components/export/ExportComponent.ts +70 -0
  15. package/components/export/ExportComponentObjects.ts +23 -0
  16. package/components/filter/FilterForm.ts +11 -0
  17. package/components/filter/FindForm.ts +11 -0
  18. package/components/form/Form.ts +31 -0
  19. package/components/grid/Grid.ts +221 -0
  20. package/components/grid/GridObjects.ts +45 -0
  21. package/components/index.ts +6 -0
  22. package/components/pop-up/PopUpComponent.ts +14 -0
  23. package/components/pop-up/PopUpComponentModels.ts +13 -0
  24. package/components/tab/Tab.ts +29 -0
  25. package/components/task/TaskBar.ts +11 -0
  26. package/components/task/TaskTiles.ts +11 -0
  27. package/config.json +10 -0
  28. package/dist/Testwise.d.ts +2 -0
  29. package/dist/Testwise.js +14 -0
  30. package/dist/Testwise.js.map +1 -0
  31. package/dist/components/BaseComponent.d.ts +29 -0
  32. package/dist/components/BaseComponent.js +67 -0
  33. package/dist/components/BaseComponent.js.map +1 -0
  34. package/dist/components/BaseComponentObjects.d.ts +6 -0
  35. package/dist/components/BaseComponentObjects.js +6 -0
  36. package/dist/components/BaseComponentObjects.js.map +1 -0
  37. package/dist/components/IComponentObjects.d.ts +4 -0
  38. package/dist/components/IComponentObjects.js +2 -0
  39. package/dist/components/IComponentObjects.js.map +1 -0
  40. package/dist/components/Splitter.d.ts +5 -0
  41. package/dist/components/Splitter.js +10 -0
  42. package/dist/components/Splitter.js.map +1 -0
  43. package/dist/components/TSFComponent.d.ts +6 -0
  44. package/dist/components/TSFComponent.js +2 -0
  45. package/dist/components/TSFComponent.js.map +1 -0
  46. package/dist/components/actionbar/Actionbar.d.ts +27 -0
  47. package/dist/components/actionbar/Actionbar.js +35 -0
  48. package/dist/components/actionbar/Actionbar.js.map +1 -0
  49. package/dist/components/actionbar/ActionbarObjects.d.ts +24 -0
  50. package/dist/components/actionbar/ActionbarObjects.js +30 -0
  51. package/dist/components/actionbar/ActionbarObjects.js.map +1 -0
  52. package/dist/components/export/ExportComponent.d.ts +12 -0
  53. package/dist/components/export/ExportComponent.js +55 -0
  54. package/dist/components/export/ExportComponent.js.map +1 -0
  55. package/dist/components/export/ExportComponentObjects.d.ts +11 -0
  56. package/dist/components/export/ExportComponentObjects.js +12 -0
  57. package/dist/components/export/ExportComponentObjects.js.map +1 -0
  58. package/dist/components/form/Form.d.ts +7 -0
  59. package/dist/components/form/Form.js +31 -0
  60. package/dist/components/form/Form.js.map +1 -0
  61. package/dist/components/grid/Grid.d.ts +93 -0
  62. package/dist/components/grid/Grid.js +183 -0
  63. package/dist/components/grid/Grid.js.map +1 -0
  64. package/dist/components/grid/GridObjects.d.ts +20 -0
  65. package/dist/components/grid/GridObjects.js +25 -0
  66. package/dist/components/grid/GridObjects.js.map +1 -0
  67. package/dist/components/index.d.ts +6 -0
  68. package/dist/components/index.js +7 -0
  69. package/dist/components/index.js.map +1 -0
  70. package/dist/components/pop-up/PopUpComponent.d.ts +7 -0
  71. package/dist/components/pop-up/PopUpComponent.js +9 -0
  72. package/dist/components/pop-up/PopUpComponent.js.map +1 -0
  73. package/dist/components/pop-up/PopUpComponentModels.d.ts +7 -0
  74. package/dist/components/pop-up/PopUpComponentModels.js +8 -0
  75. package/dist/components/pop-up/PopUpComponentModels.js.map +1 -0
  76. package/dist/config.json +10 -0
  77. package/dist/enums/ActionbarRegions.d.ts +5 -0
  78. package/dist/enums/ActionbarRegions.js +7 -0
  79. package/dist/enums/ActionbarRegions.js.map +1 -0
  80. package/dist/enums/ButtonEnums.d.ts +24 -0
  81. package/dist/enums/ButtonEnums.js +29 -0
  82. package/dist/enums/ButtonEnums.js.map +1 -0
  83. package/dist/enums/ExportFormat.d.ts +5 -0
  84. package/dist/enums/ExportFormat.js +7 -0
  85. package/dist/enums/ExportFormat.js.map +1 -0
  86. package/dist/enums/LogLevel.d.ts +8 -0
  87. package/dist/enums/LogLevel.js +11 -0
  88. package/dist/enums/LogLevel.js.map +1 -0
  89. package/dist/example-code/TestifyService.d.ts +22 -0
  90. package/dist/example-code/TestifyService.js +191 -0
  91. package/dist/example-code/TestifyService.js.map +1 -0
  92. package/dist/helpers/InflightRequestTracker.d.ts +10 -0
  93. package/dist/helpers/InflightRequestTracker.js +26 -0
  94. package/dist/helpers/InflightRequestTracker.js.map +1 -0
  95. package/dist/helpers/LoginHelper.d.ts +38 -0
  96. package/dist/helpers/LoginHelper.js +112 -0
  97. package/dist/helpers/LoginHelper.js.map +1 -0
  98. package/dist/helpers/UserSimulationHelper.d.ts +20 -0
  99. package/dist/helpers/UserSimulationHelper.js +62 -0
  100. package/dist/helpers/UserSimulationHelper.js.map +1 -0
  101. package/dist/index.d.ts +34 -0
  102. package/dist/index.js +26 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/page-extensions/GoToDeepLink.d.ts +11 -0
  105. package/dist/page-extensions/GoToDeepLink.js +17 -0
  106. package/dist/page-extensions/GoToDeepLink.js.map +1 -0
  107. package/dist/page-extensions/LoginFeatures.d.ts +16 -0
  108. package/dist/page-extensions/LoginFeatures.js +30 -0
  109. package/dist/page-extensions/LoginFeatures.js.map +1 -0
  110. package/dist/page-extensions/UserSimulation.d.ts +15 -0
  111. package/dist/page-extensions/UserSimulation.js +30 -0
  112. package/dist/page-extensions/UserSimulation.js.map +1 -0
  113. package/dist/page-overrides/ClickOverride.d.ts +6 -0
  114. package/dist/page-overrides/ClickOverride.js +73 -0
  115. package/dist/page-overrides/ClickOverride.js.map +1 -0
  116. package/dist/services/ConfigBuilder.d.ts +12 -0
  117. package/dist/services/ConfigBuilder.js +30 -0
  118. package/dist/services/ConfigBuilder.js.map +1 -0
  119. package/dist/services/Logger.d.ts +8 -0
  120. package/dist/services/Logger.js +106 -0
  121. package/dist/services/Logger.js.map +1 -0
  122. package/dist/services/ReportingService.d.ts +8 -0
  123. package/dist/services/ReportingService.js +29 -0
  124. package/dist/services/ReportingService.js.map +1 -0
  125. package/dist/types/CoreTypes.d.ts +2 -0
  126. package/dist/types/CoreTypes.js +2 -0
  127. package/dist/types/CoreTypes.js.map +1 -0
  128. package/dist/types/universal.d.ts +25 -0
  129. package/dist/types/universal.js +2 -0
  130. package/dist/types/universal.js.map +1 -0
  131. package/enums/ActionbarRegions.ts +5 -0
  132. package/enums/ButtonEnums.ts +28 -0
  133. package/enums/ExportFormat.ts +5 -0
  134. package/enums/LogLevel.ts +9 -0
  135. package/example-code/TestifyService.ts +201 -0
  136. package/helpers/InflightRequestTracker.ts +33 -0
  137. package/helpers/LoginHelper.ts +140 -0
  138. package/helpers/UserSimulationHelper.ts +72 -0
  139. package/index.ts +28 -0
  140. package/package.json +40 -0
  141. package/page-extensions/GoToDeepLink.ts +29 -0
  142. package/page-extensions/LoginFeatures.ts +50 -0
  143. package/page-extensions/UserSimulation.ts +49 -0
  144. package/page-overrides/ClickOverride.ts +89 -0
  145. package/promptCredentials.js +113 -0
  146. package/scripts/Testwise.template.json +19 -0
  147. package/scripts/add-config.js +23 -0
  148. package/services/ConfigBuilder.ts +40 -0
  149. package/services/Logger.ts +125 -0
  150. package/services/ReportingService.ts +41 -0
  151. package/types/CoreTypes.ts +9 -0
  152. package/types/universal.ts +26 -0
@@ -0,0 +1,201 @@
1
+ /* Ignore this file for reviewing, it is used for Testify integration and only serves as an example*/
2
+
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ import axios, { type AxiosInstance } from 'axios';
5
+
6
+ export class TestifyService {
7
+ private token: string;
8
+ private projectId: string;
9
+ private baseUrl: string;
10
+ private axiosInstance: AxiosInstance;
11
+ testRunId = '';
12
+
13
+ constructor() {
14
+ this.token = `Bearer ${process.env.TESTIFY_TOKEN}`;
15
+ this.projectId = process.env.TESTIFY_PROJECT_ID || '';
16
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
17
+ this.baseUrl = process.env.TESTIFY_BASE_URL!.replace(/\/$/, '');
18
+ this.axiosInstance = axios.create({
19
+ baseURL: this.baseUrl,
20
+ headers: {
21
+ Authorization: this.token,
22
+ accept: '*/*'
23
+ }
24
+ });
25
+ }
26
+
27
+ public async createTestRun(): Promise<void> {
28
+ try {
29
+ const projectName = await this.getProjectName();
30
+ const testRunName = `${projectName} ${new Date().toISOString()}`;
31
+
32
+ if (!this.testRunId) {
33
+ await this.createTestRunRequest(testRunName);
34
+ this.testRunId = await this.getTestRunId(testRunName);
35
+ }
36
+
37
+ await new Promise((res) => setTimeout(res, 1000));
38
+ } catch (err) {
39
+ console.error('createTestRun failed:', err);
40
+ }
41
+ }
42
+
43
+ private async getTestRunId(testRunName: string): Promise<string> {
44
+ try {
45
+ const url = `/api/v1/TestRun?projectId=${this.projectId}`;
46
+ const response = await this.axiosInstance.get(url, { headers: this.headers('text/plain') });
47
+ const testRunList = response.data;
48
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
49
+ const found = testRunList.find((tr: any) => tr.name?.toLowerCase() === testRunName.toLowerCase());
50
+ return found?.id?.toString() ?? '';
51
+ } catch (err) {
52
+ console.error('getTestRunId failed:', err);
53
+ return '';
54
+ }
55
+ }
56
+
57
+ private async createTestRunRequest(testRunName: string) {
58
+ try {
59
+ const encodedName = encodeURIComponent(testRunName);
60
+ const url = `/api/v1/TestRun/Create?testRunName=${encodedName}&projectId=${this.projectId}`;
61
+ await this.axiosInstance.post(url, null, { headers: this.headers() });
62
+ } catch (err) {
63
+ console.error('createTestRunRequest failed:', err);
64
+ }
65
+ }
66
+
67
+ private async getProjectName(): Promise<string> {
68
+ try {
69
+ const url = `/api/v1/Project/${this.projectId}`;
70
+ const response = await this.axiosInstance.get(url, { headers: this.headers('text/plain') });
71
+ const project = response.data;
72
+ return project.name;
73
+ } catch (err) {
74
+ console.error('getProjectName failed:', err);
75
+ return '';
76
+ }
77
+ }
78
+
79
+ async reportTestResult(testName: string, passed: boolean) {
80
+ try {
81
+ const testCases = await this.getTestCases();
82
+ if (!this.testCaseExists(testCases, testName)) {
83
+ await this.createTestCase(testName);
84
+ }
85
+ const testCaseId = await this.getTestCaseId(testName);
86
+ if (!testCaseId) {
87
+ console.error(`Could not find or create test case ID for "${testName}"`);
88
+ return;
89
+ }
90
+ await this.addTestCaseToTestRun(testCaseId);
91
+ const testResultId = await this.getTestResultId(testCaseId);
92
+ if (!testResultId) {
93
+ console.error(`Could not find test result ID for test case "${testName}"`);
94
+ return;
95
+ }
96
+ await this.updateResult(testResultId, passed);
97
+
98
+ await new Promise((res) => setTimeout(res, 200));
99
+ } catch (err) {
100
+ console.error('reportTestResult failed:', err);
101
+ }
102
+ }
103
+
104
+ private async updateResult(testResultId: string, passed: boolean) {
105
+ try {
106
+ const status = passed ? '1' : '2';
107
+ const url = `/api/v1/Result/${testResultId}/UpdateStatus?newStatus=${status}`;
108
+ await this.axiosInstance.put(url, null, { headers: this.headers() });
109
+ } catch (err) {
110
+ console.error('updateResult failed:', err);
111
+ }
112
+ }
113
+
114
+ private async getTestResultId(testCaseId: string): Promise<string> {
115
+ try {
116
+ const url = `/api/v1/Result?testRunId=${this.testRunId}`;
117
+ const response = await this.axiosInstance.get(url, { headers: this.headers('text/plain') });
118
+ const results = response.data;
119
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
120
+ const found = results.find((r: any) => r.testCaseId?.toString() === testCaseId);
121
+ return found?.id?.toString() ?? '';
122
+ } catch (err) {
123
+ console.error('getTestResultId failed:', err);
124
+ return '';
125
+ }
126
+ }
127
+
128
+ private async getTestCaseId(testCaseName: string): Promise<string> {
129
+ try {
130
+ const testCases = await this.getTestCases();
131
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
132
+ const found = testCases.find((tc: any) => tc.name === testCaseName);
133
+ return found?.id?.toString() ?? '';
134
+ } catch (err) {
135
+ console.error('getTestCaseId failed:', err);
136
+ return '';
137
+ }
138
+ }
139
+
140
+ private async addTestCaseToTestRun(testCaseId: string) {
141
+ try {
142
+ const url = `/api/v1/Result/Create?caseId=${testCaseId}&testRunId=${this.testRunId}`;
143
+ await this.axiosInstance.post(url, null, { headers: this.headers() });
144
+ } catch (err) {
145
+ console.error('addTestCaseToTestRun failed:', err);
146
+ }
147
+ }
148
+
149
+ private async createTestCase(testCaseName: string) {
150
+ try {
151
+ const encodedName = encodeURIComponent(testCaseName);
152
+ const url = `/api/v1/TestCase/Create?testCaseName=${encodedName}&projectId=${this.projectId}`;
153
+ await this.axiosInstance.post(url, null, { headers: this.headers() });
154
+ } catch (err) {
155
+ console.error('createTestCase failed:', err);
156
+ }
157
+ }
158
+
159
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
160
+ private testCaseExists(testCases: any[], testName: string): boolean {
161
+ try {
162
+ return testCases.some((tc) => tc.name?.toLowerCase() === testName.toLowerCase());
163
+ } catch (err) {
164
+ console.error('testCaseExists failed:', err);
165
+ return false;
166
+ }
167
+ }
168
+
169
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
170
+ private async getTestCases(): Promise<any[]> {
171
+ try {
172
+ const url = `/api/v1/TestCase?projectId=${this.projectId}`;
173
+ const response = await this.axiosInstance.get(url, { headers: this.headers('text/plain') });
174
+ return response.data;
175
+ } catch (err) {
176
+ console.error('getTestCases failed:', err);
177
+ return [];
178
+ }
179
+ }
180
+
181
+ public async closeTestRun(): Promise<void> {
182
+ if (!this.testRunId) {
183
+ console.error('closeTestRun failed: testRunId is not set.');
184
+ return;
185
+ }
186
+ try {
187
+ const url = `/api/v1/TestRun/${this.testRunId}/Close`;
188
+ await this.axiosInstance.put(url, null, { headers: this.headers() });
189
+ console.info(`Test run ${this.testRunId} closed successfully.`);
190
+ } catch (err) {
191
+ console.error('closeTestRun failed:', err);
192
+ }
193
+ }
194
+
195
+ private headers(accept = '*/*') {
196
+ return {
197
+ accept,
198
+ Authorization: this.token
199
+ };
200
+ }
201
+ }
@@ -0,0 +1,33 @@
1
+ import type { Page, Request } from '@playwright/test';
2
+
3
+ export class InflightRequestsTracker {
4
+ private _page: Page;
5
+ private _requests: Set<Request>;
6
+
7
+ constructor(page: Page) {
8
+ this._page = page;
9
+ this._requests = new Set();
10
+ this._onStarted = this._onStarted.bind(this);
11
+ this._onFinished = this._onFinished.bind(this);
12
+ this._page.on('request', this._onStarted);
13
+ this._page.on('requestfinished', this._onFinished);
14
+ this._page.on('requestfailed', this._onFinished);
15
+ }
16
+
17
+ _onStarted(request: Request) {
18
+ this._requests.add(request);
19
+ }
20
+ _onFinished(request: Request) {
21
+ this._requests.delete(request);
22
+ }
23
+
24
+ inflightRequests(): Request[] {
25
+ return Array.from(this._requests);
26
+ }
27
+
28
+ dispose() {
29
+ this._page.removeListener('request', this._onStarted);
30
+ this._page.removeListener('requestfinished', this._onFinished);
31
+ this._page.removeListener('requestfailed', this._onFinished);
32
+ }
33
+ }
@@ -0,0 +1,140 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { type Page, expect } from '@playwright/test';
5
+ import { testwiseConfig } from '../services/ConfigBuilder.js';
6
+ import type { UniversalConfigOptions, UniversalLoginOptions } from '../types/universal.js';
7
+
8
+ const _dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const configFixture = JSON.parse(fs.readFileSync(path.resolve(_dirname, '../config.json'), 'utf-8'));
10
+
11
+ export class LoginHelper {
12
+ private _page: Page;
13
+ private _serviceUrl: string;
14
+ private _metaEndpoint: string;
15
+
16
+ constructor(page: Page) {
17
+ this._page = page;
18
+
19
+ const serviceUrl = testwiseConfig().get<string>('environmentSettings.serviceUrl');
20
+ const metaEndpoint = testwiseConfig().get<string>('environmentSettings.metaEndpoint');
21
+
22
+ if (!serviceUrl || !metaEndpoint)
23
+ throw new Error(
24
+ 'Either environmentSettings.serviceUrl or environmentSettings.metaEndpoint is not defined in the configuration.'
25
+ );
26
+
27
+ this._serviceUrl = serviceUrl;
28
+ this._metaEndpoint = metaEndpoint;
29
+ }
30
+
31
+ /**
32
+ * Log in and go to a specified application
33
+ * @param applicationName - Application name to launch
34
+ */
35
+ public async logIn(applicationName: string = configFixture.defaultApplication) {
36
+ await this.logInAndConfigureApplication({
37
+ config: {
38
+ defaultApplication: applicationName
39
+ }
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Log in with credentials
45
+ * @param username - The username to log in with
46
+ * @param password - The password to log in with
47
+ */
48
+ public async logInWithCredentials(username: string, password: string) {
49
+ const options: UniversalLoginOptions = {
50
+ username: username,
51
+ password: password
52
+ };
53
+
54
+ await this.logInWithOptions(options);
55
+ }
56
+
57
+ /**
58
+ * Log in with options
59
+ * @param options - The login options
60
+ */
61
+ public async logInWithOptions(options: UniversalLoginOptions) {
62
+ await this.logInAndConfigureApplication(options);
63
+ }
64
+
65
+ /**
66
+ * Navigate to the home page and wait for the menu to load
67
+ */
68
+ private async navigateHomeAndWaitForMenuToLoad() {
69
+ await this._page.goto('/');
70
+
71
+ await this._page.route('**/i_ui_menu?**', (route) => {
72
+ route.continue();
73
+ });
74
+
75
+ await expect.poll(async () => await this._page.url()).toContain('#application=');
76
+ }
77
+
78
+ /**
79
+ * Log in and configure the application
80
+ * @param options - The login options
81
+ */
82
+ private async logInAndConfigureApplication(options: UniversalLoginOptions = {}) {
83
+ const { serviceUrl, metaEndpoint, config } = options;
84
+
85
+ const username = options.username ?? process.env.SF_TEST_USERNAME;
86
+ const password = options.password ?? process.env.SF_TEST_PASSWORD;
87
+
88
+ if (!username || !password) {
89
+ throw new Error('Username and password are required for login.');
90
+ }
91
+
92
+ if (serviceUrl && metaEndpoint) {
93
+ this._serviceUrl = serviceUrl;
94
+ this._metaEndpoint = metaEndpoint;
95
+ }
96
+
97
+ const response = await this._page.request.post(`${this._serviceUrl}/account/api/login`, {
98
+ form: {
99
+ UserName: username,
100
+ Password: password
101
+ }
102
+ });
103
+
104
+ if (response.status() !== 204) {
105
+ console.error('Login failed', await response.status());
106
+ }
107
+
108
+ // Set the navigator.language property
109
+ await this._page.route('**/*', (route, request) => {
110
+ route.continue({
111
+ headers: {
112
+ ...request.headers(),
113
+ 'Accept-Language': 'en-US'
114
+ }
115
+ });
116
+ });
117
+
118
+ await this.configureApplication(config ?? {});
119
+
120
+ await this.navigateHomeAndWaitForMenuToLoad();
121
+ }
122
+
123
+ /**
124
+ * Configure the application
125
+ * @param options - The configuration options
126
+ */
127
+ private async configureApplication(options: UniversalConfigOptions) {
128
+ await this._page.route('**/config.json', (route) => {
129
+ route.fulfill({
130
+ status: 200,
131
+ contentType: 'application/json',
132
+ body: JSON.stringify({
133
+ ...configFixture,
134
+ serviceUrl: `${this._serviceUrl}/iam/${this._metaEndpoint}`,
135
+ ...options
136
+ })
137
+ });
138
+ });
139
+ }
140
+ }
@@ -0,0 +1,72 @@
1
+ import { type Page, expect } from '@playwright/test';
2
+ import { testwiseConfig } from '../services/ConfigBuilder.js';
3
+
4
+ export class UserSimulationHelper {
5
+ private _page: Page;
6
+ private _serviceUrl: string;
7
+
8
+ constructor(page: Page) {
9
+ this._page = page;
10
+
11
+ const serviceUrl = testwiseConfig().get<string>('environmentSettings.serviceUrl');
12
+
13
+ if (!serviceUrl) throw new Error('environmentSettings.serviceUrl is not defined in the configuration.');
14
+
15
+ this._serviceUrl = serviceUrl;
16
+ }
17
+
18
+ /**
19
+ * Get the simulated user
20
+ * @returns The simulated user name
21
+ */
22
+ public async getSimulatedUser(): Promise<string> {
23
+ const response = await this._page.request.get(`${this._serviceUrl}/account/api/usersimulation`);
24
+
25
+ switch (response.status()) {
26
+ case 204:
27
+ console.error('User simulation not active', await response.status());
28
+ return '';
29
+ case 200:
30
+ return (await response.json()).simulated_user;
31
+ default:
32
+ throw new Error(`Get simulated user request failed with status: ${await response.status()}`);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Stop the user simulation
38
+ */
39
+ public async stopSimulation(): Promise<void> {
40
+ const response = await this._page.request.delete(`${this._serviceUrl}/account/api/usersimulation`);
41
+
42
+ if (response.status() === 204) {
43
+ console.info('STOP User simulation request is successful', await response.status());
44
+ } else {
45
+ throw new Error(`STOP User simulation has unexpected status: ${await response.status()}`);
46
+ }
47
+
48
+ await expect.poll(async () => await this.getSimulatedUser()).toBe('');
49
+ }
50
+
51
+ /**
52
+ * Simulate a user
53
+ * @param userName - The name of the user to simulate
54
+ */
55
+ public async simulateUser(userName: string): Promise<void> {
56
+ const response = await this._page.request.patch(`${this._serviceUrl}/account/api/usersimulation`, {
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ Accept: 'application/json'
60
+ },
61
+ data: JSON.stringify({
62
+ simulated_user: userName
63
+ })
64
+ });
65
+
66
+ if (response.status() === 200) {
67
+ console.info('User simulation request is successful', await response.status());
68
+ } else {
69
+ throw new Error(`User simulation has unexpected status: ${await response.status()}`);
70
+ }
71
+ }
72
+ }
package/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { logger } from './services/Logger.js';
2
+
3
+ const originalLog = console.log;
4
+ const originalInfo = console.info;
5
+ const originalWarn = console.warn;
6
+ const originalError = console.error;
7
+ const originalDebug = console.debug;
8
+
9
+ console.log = (...args: unknown[]) => logger.info(args.join(' '));
10
+ console.info = (...args: unknown[]) => logger.info(args.join(' '));
11
+ console.warn = (...args: unknown[]) => logger.warn(args.join(' '));
12
+ console.error = (...args: unknown[]) => logger.error(args.join(' '));
13
+ console.debug = (...args: unknown[]) => logger.debug(args.join(' '));
14
+
15
+ export { originalLog, originalInfo, originalWarn, originalError, originalDebug };
16
+ export * from './services/ConfigBuilder.js';
17
+ export * from './Testwise.js';
18
+ export * from './helpers/LoginHelper.js';
19
+ export * from './page-extensions/GoToDeepLink.js';
20
+ export * from './page-extensions/LoginFeatures.js';
21
+ export * from './page-extensions/UserSimulation.js';
22
+ export * from './page-overrides/ClickOverride.js';
23
+ export * from './types/CoreTypes.js';
24
+ export * from './types/universal.js';
25
+ export * from './enums/ButtonEnums.js';
26
+ export * from './enums/ActionbarRegions.js';
27
+ export * from './enums/ExportFormat.js';
28
+ export * from './components/index.js';
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@thinkwise/testwise",
3
+ "version": "0.0.2-alpha.2",
4
+ "main": "dist/index.js",
5
+ "type": "module",
6
+ "bin": {
7
+ "testwise": "./scripts/add-config.js",
8
+ "testwise-credentials": "./promptCredentials.js"
9
+ },
10
+ "scripts": {
11
+ "lint": "eslint ./ --cache --ignore-pattern .gitignore",
12
+ "format:fix": "run-s \"lint --fix\" && biome check --fix --unsafe",
13
+ "format:check": "run-s \"lint \" && biome check"
14
+ },
15
+ "keywords": [],
16
+ "author": "",
17
+ "license": "ISC",
18
+ "description": "",
19
+ "dependencies": {
20
+ "@types/node": "^22.15.29",
21
+ "axios": "^1.9.0",
22
+ "chalk": "^5.4.1",
23
+ "csv-parse": "^5.6.0",
24
+ "prompts": "^2.4.2"
25
+ },
26
+ "peerDependencies": {
27
+ "@playwright/test": "^1.52.0"
28
+ },
29
+ "devDependencies": {
30
+ "@biomejs/biome": "^1.9.4",
31
+ "@eslint/js": "^9.28.0",
32
+ "@playwright/test": "^1.52.0",
33
+ "biome": "^0.3.3",
34
+ "eslint": "^9.28.0",
35
+ "eslint-plugin-playwright": "^2.2.0",
36
+ "npm-run-all": "^4.1.5",
37
+ "typescript-eslint": "^8.34.0"
38
+ },
39
+ "publishConfig": { "access": "public" }
40
+ }
@@ -0,0 +1,29 @@
1
+ import type { Test } from '../types/CoreTypes.js';
2
+
3
+ declare module '@playwright/test' {
4
+ interface Page {
5
+ goToDeepLink(deepLink: string): Promise<null | import('@playwright/test').Response>;
6
+ }
7
+ }
8
+
9
+ export class GoToDeepLink {
10
+ private _test: Test;
11
+
12
+ constructor(test: Test) {
13
+ this._test = test.extend({
14
+ page: async ({ page }, use) => {
15
+ page.goToDeepLink = async (deepLink: string): Promise<null | import('@playwright/test').Response> => {
16
+ const url = new URL(await page.url());
17
+
18
+ return await page.goto(`${url}/${deepLink}`);
19
+ };
20
+
21
+ await use(page);
22
+ }
23
+ });
24
+ }
25
+
26
+ get test() {
27
+ return this._test;
28
+ }
29
+ }
@@ -0,0 +1,50 @@
1
+ import { LoginHelper } from '../helpers/LoginHelper.js';
2
+ import type { Test } from '../types/CoreTypes.js';
3
+ import type { UniversalLoginOptions } from '../types/universal.js';
4
+
5
+ declare module '@playwright/test' {
6
+ interface Page {
7
+ logIn(): Promise<void>;
8
+ logInWithCredentials(username: string, password: string): Promise<void>;
9
+ logInWithOptions(options: UniversalLoginOptions): Promise<void>;
10
+ }
11
+ }
12
+
13
+ export class LoginFeatures {
14
+ private _loginHelper: LoginHelper | undefined;
15
+ private _test: Test;
16
+
17
+ constructor(test: Test) {
18
+ this._test = test.extend({
19
+ page: async ({ page }, use) => {
20
+ this._loginHelper = new LoginHelper(page);
21
+
22
+ page.logIn = async () => {
23
+ await this.loginHelper.logIn();
24
+ };
25
+
26
+ page.logInWithCredentials = async (username: string, password: string) => {
27
+ await this.loginHelper.logInWithCredentials(username, password);
28
+ };
29
+
30
+ page.logInWithOptions = async (options: UniversalLoginOptions) => {
31
+ await this.loginHelper.logInWithOptions(options);
32
+ };
33
+
34
+ await use(page);
35
+ }
36
+ });
37
+ }
38
+
39
+ private get loginHelper(): LoginHelper {
40
+ if (!this._loginHelper) {
41
+ throw new Error('LoginHelper is not initialized');
42
+ }
43
+
44
+ return this._loginHelper;
45
+ }
46
+
47
+ get test() {
48
+ return this._test;
49
+ }
50
+ }
@@ -0,0 +1,49 @@
1
+ import { UserSimulationHelper } from '../helpers/UserSimulationHelper.js';
2
+ import type { Test } from '../types/CoreTypes.js';
3
+
4
+ declare module '@playwright/test' {
5
+ interface Page {
6
+ simulateUser(username: string): Promise<void>;
7
+ getSimulatedUser(): Promise<string>;
8
+ stopSimulation(): Promise<void>;
9
+ }
10
+ }
11
+
12
+ export class UserSimulation {
13
+ private _userSimulationHelper: UserSimulationHelper | undefined;
14
+ private _test: Test;
15
+
16
+ constructor(test: Test) {
17
+ this._test = test.extend({
18
+ page: async ({ page }, use) => {
19
+ this._userSimulationHelper = new UserSimulationHelper(page);
20
+
21
+ page.simulateUser = async (username: string) => {
22
+ await this.userSimulationHelper.simulateUser(username);
23
+ };
24
+
25
+ page.getSimulatedUser = async () => {
26
+ return await this.userSimulationHelper.getSimulatedUser();
27
+ };
28
+
29
+ page.stopSimulation = async () => {
30
+ await this.userSimulationHelper.stopSimulation();
31
+ };
32
+
33
+ await use(page);
34
+ }
35
+ });
36
+ }
37
+
38
+ private get userSimulationHelper(): UserSimulationHelper {
39
+ if (!this._userSimulationHelper) {
40
+ throw new Error('UserSimulationHelper is not initialized');
41
+ }
42
+
43
+ return this._userSimulationHelper;
44
+ }
45
+
46
+ get test() {
47
+ return this._test;
48
+ }
49
+ }