@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,89 @@
1
+ import { type Locator, expect } from '@playwright/test';
2
+ import { InflightRequestsTracker } from '../helpers/InflightRequestTracker.js';
3
+ import { testwiseConfig } from '../services/ConfigBuilder.js';
4
+ import type { Test } from '../types/CoreTypes.js';
5
+
6
+ export class ClickOverride {
7
+ private _test: Test;
8
+
9
+ constructor(test: Test) {
10
+ this._test = test.extend({
11
+ page: async ({ page }, use) => {
12
+ const overrideClickMethod = (locator: Locator) => {
13
+ const originalClickMethod = locator.click.bind(locator);
14
+
15
+ locator.click = async (options) => {
16
+ if (testwiseConfig().get<boolean>('InflightRequests.waitForRequestsToComplete') === true) {
17
+ const tracker = new InflightRequestsTracker(page);
18
+
19
+ await Promise.all([
20
+ originalClickMethod(options),
21
+
22
+ // Ensure at least one XHR request is issued
23
+ Promise.race([
24
+ page.waitForRequest((request) => request.resourceType() === 'xhr'),
25
+ new Promise((resolve) => setTimeout(resolve, 50)) // Wait briefly to ensure the XHR request is in flight
26
+ ])
27
+ ]);
28
+
29
+ // Wait for all XHR requests to finish
30
+ try {
31
+ await expect
32
+ .poll(() => tracker.inflightRequests().filter((request) => request.resourceType() === 'xhr').length, {
33
+ timeout: 3000
34
+ })
35
+ .toBe(0);
36
+ } catch (e) {
37
+ console.warn('Exception while waiting for inflight requests to finish:', e);
38
+
39
+ if (testwiseConfig().get<boolean>('InflightRequests.swallowPollingExceptions') === false) throw e;
40
+ }
41
+ } else {
42
+ await originalClickMethod(options);
43
+ }
44
+ };
45
+
46
+ return locator;
47
+ };
48
+
49
+ // Override the getByTestId.click method
50
+ const originalGetByTestId = page.getByTestId.bind(page);
51
+ page.getByTestId = (...args) => overrideClickMethod(originalGetByTestId(...args));
52
+
53
+ // Override the locator.click method
54
+ const originalLocator = page.locator.bind(page);
55
+ page.locator = (...args) => overrideClickMethod(originalLocator(...args));
56
+
57
+ // Override the getByAltText.click method
58
+ const originalGetByAltText = page.getByAltText.bind(page);
59
+ page.getByAltText = (...args) => overrideClickMethod(originalGetByAltText(...args));
60
+
61
+ // Override the getByLabel.click method
62
+ const originalGetByLabel = page.getByLabel.bind(page);
63
+ page.getByLabel = (...args) => overrideClickMethod(originalGetByLabel(...args));
64
+
65
+ // Override the getByRole.click method
66
+ const originalGetByRole = page.getByRole.bind(page);
67
+ page.getByRole = (...args) => overrideClickMethod(originalGetByRole(...args));
68
+
69
+ // Override the getByPlaceholder.click method
70
+ const originalGetByPlaceholder = page.getByPlaceholder.bind(page);
71
+ page.getByPlaceholder = (...args) => overrideClickMethod(originalGetByPlaceholder(...args));
72
+
73
+ // Override the getByText.click method
74
+ const originalGetByText = page.getByText.bind(page);
75
+ page.getByText = (...args) => overrideClickMethod(originalGetByText(...args));
76
+
77
+ // Override the getByTitle.click method
78
+ const originalGetByTitle = page.getByTitle.bind(page);
79
+ page.getByTitle = (...args) => overrideClickMethod(originalGetByTitle(...args));
80
+
81
+ await use(page);
82
+ }
83
+ });
84
+ }
85
+
86
+ get test() {
87
+ return this._test;
88
+ }
89
+ }
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ import chalk from 'chalk';
3
+ import process from 'node:process';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { readFileSync } from 'node:fs';
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(' ')}`, { shell: true, stdio: 'inherit' });
18
+ process.exit();
19
+ }
20
+
21
+ const args = process.argv.slice(2);
22
+
23
+ if (!args.length) {
24
+ exitWithError('Error: Got too few parameters. Usage: yarn test:credentials test');
25
+ }
26
+
27
+ const yarnScriptToExec = args[0];
28
+
29
+ if (!packageScripts[yarnScriptToExec]) {
30
+ exitWithError(`Error: Given script "${yarnScriptToExec}" doesn't exist in package.json.`);
31
+ }
32
+
33
+ // INCOMING HACK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
34
+ // temp fix to be reworked when converting this file to TS
35
+ import fs from 'node:fs';
36
+ import path from 'node:path';
37
+
38
+ const indiciumURL = (() => {
39
+ try {
40
+ const configPath = path.resolve(process.cwd(), './Testwise.json');
41
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
42
+ return config.environmentSettings?.serviceUrl || '';
43
+ } catch (error) {
44
+ console.error('Error reading Testwise.json:', error);
45
+ return '';
46
+ }
47
+ })();
48
+
49
+ // END OF HACK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
50
+
51
+ if (process.env.SF_TEST_USERNAME && process.env.SF_TEST_PASSWORD) {
52
+ console.log('Using SF_TEST_USERNAME and SF_TEST_PASSWORD environment variables.');
53
+ startTestSolution();
54
+ }
55
+
56
+ const questions = [{
57
+ type: 'text',
58
+ name: 'username',
59
+ message: 'Username:',
60
+ validate: (value) => {
61
+ if (!value?.length) {
62
+ return 'Username is required';
63
+ }
64
+
65
+ if (value.toLowerCase() === 'demo') {
66
+ return 'Running tests locally with demo is forbidden as it results in dataset concurrency problems'
67
+ }
68
+
69
+ return true;
70
+ }
71
+ }, {
72
+ type: 'invisible',
73
+ name: 'password',
74
+ message: 'Password:',
75
+ validate: (value) => !value?.length ? 'Password is required' : true
76
+ }];
77
+
78
+ (async () => {
79
+ const credentialsResponse = await prompts(questions);
80
+
81
+ if (!credentialsResponse || !credentialsResponse.username || !credentialsResponse.password) {
82
+ exitWithError('Username or password was not given');
83
+ }
84
+
85
+ const { password, username } = credentialsResponse;
86
+ const params = new URLSearchParams();
87
+
88
+ params.append('UserName', username);
89
+ params.append('Password', password);
90
+
91
+ const fetch = (await import('node-fetch')).default;
92
+ const authResponse = await fetch(`${indiciumURL}/account/api/login`, { method: 'POST', body: params });
93
+
94
+ if (authResponse.status === 204) {
95
+ console.log('Confirmed credentials work @', indiciumURL);
96
+ } else {
97
+ const confirmResponse = await prompts([{
98
+ type: 'confirm',
99
+ name: 'value',
100
+ message: `Can't login to ${indiciumURL} with the given credentials. Continue anyways?`,
101
+ initial: true
102
+ }]);
103
+
104
+ if (!confirmResponse.value) {
105
+ return;
106
+ }
107
+ }
108
+
109
+ process.env.SF_TEST_USERNAME = username;
110
+ process.env.SF_TEST_PASSWORD = password;
111
+
112
+ startTestSolution();
113
+ })();
@@ -0,0 +1,19 @@
1
+ {
2
+ "environmentSettings" :{
3
+ "baseUrl" : "https://develop.example.app",
4
+ "serviceUrl" : "https://develop.example.app/service",
5
+ "metaEndpoint" : "iam"
6
+ },
7
+ "featureSettings" :{
8
+ "logger" : {
9
+ "logDir" : "logs",
10
+ "logLevel" : "info",
11
+ "logByDay" : true,
12
+ "bufferSizeLimit" : -1
13
+ },
14
+ "InflightRequests" : {
15
+ "waitForRequestsToComplete" : true,
16
+ "swallowPollingExceptions" : true
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-undef */
3
+ /* eslint-disable @typescript-eslint/naming-convention */
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ if (process.argv[2] === 'install') {
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const configFile = path.resolve(process.cwd(), 'Testwise.json');
13
+ const templateFile = path.join(__dirname, 'Testwise.template.json');
14
+
15
+ if (!fs.existsSync(configFile)) {
16
+ fs.copyFileSync(templateFile, configFile);
17
+ console.log('Testwise.json config file created.');
18
+ } else {
19
+ console.log('Testwise.json already exists, skipping creation.');
20
+ }
21
+ } else {
22
+ console.log('Unknown or missing command. Usage: npx testwise install');
23
+ }
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ class ConfigBuilder {
5
+ private config: Record<string, unknown>;
6
+
7
+ constructor() {
8
+ const configFilePath = path.resolve(process.cwd(), 'Testwise.json');
9
+
10
+ if (!fs.existsSync(configFilePath)) {
11
+ throw new Error(`Config file not found at path: ${configFilePath}`);
12
+ }
13
+
14
+ const fileContent = fs.readFileSync(configFilePath, 'utf-8');
15
+ this.config = JSON.parse(fileContent);
16
+ }
17
+
18
+ /**
19
+ * Retrieves a value from the config using a dot-separated key.
20
+ * @param key - The dot-separated key to retrieve the value.
21
+ * @returns The value associated with the key, or undefined if not found.
22
+ */
23
+ public get<T>(key: string): T | undefined {
24
+ return key
25
+ .split('.')
26
+ .reduce(
27
+ (obj: Record<string, unknown> | undefined, segment) => obj?.[segment] as Record<string, unknown>,
28
+ this.config
29
+ ) as T;
30
+ }
31
+ }
32
+
33
+ let instance: ConfigBuilder | null = null;
34
+
35
+ export function testwiseConfig(): ConfigBuilder {
36
+ if (!instance) {
37
+ instance = new ConfigBuilder();
38
+ }
39
+ return instance;
40
+ }
@@ -0,0 +1,125 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { LogLevel } from '../enums/LogLevel.js';
4
+ import { originalDebug, originalError, originalInfo, originalLog, originalWarn } from '../index.js';
5
+ import { testwiseConfig } from './ConfigBuilder.js';
6
+
7
+ const logDir = path.resolve(process.cwd(), testwiseConfig().get<string>('featureSettings.logger.logDir') ?? 'logs');
8
+
9
+ fs.mkdirSync(logDir, { recursive: true });
10
+
11
+ const sanitizedTimestamp = new Date().toISOString().split('T')[0];
12
+ const logFilePath = path.join(
13
+ logDir,
14
+ testwiseConfig().get<boolean>('featureSettings.logger.logByDay') === true
15
+ ? `test-log-${sanitizedTimestamp}.log`
16
+ : 'test-log.log'
17
+ );
18
+
19
+ let logBuffer: string[] = [];
20
+ const bufferSizeLimit = testwiseConfig().get<number>('featureSettings.logger.bufferSizeLimit') ?? 1000;
21
+
22
+ let linesAlreadyWritten = 0;
23
+
24
+ if (fs.existsSync(logFilePath)) {
25
+ const existingLogs = fs.readFileSync(logFilePath, 'utf-8').split('\n').filter(Boolean);
26
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
27
+ linesAlreadyWritten = existingLogs.length;
28
+ }
29
+
30
+ /**
31
+ * Flushes the log buffer to the log file.
32
+ * @returns {void}
33
+ * @throws {Error} If the log buffer cannot be flushed.
34
+ */
35
+ function flushLogs() {
36
+ try {
37
+ if (logBuffer.length > 0) {
38
+ const existingLogs = fs.existsSync(logFilePath)
39
+ ? fs.readFileSync(logFilePath, 'utf-8').split('\n').filter(Boolean)
40
+ : [];
41
+
42
+ const combinedLogs = [...existingLogs, ...logBuffer];
43
+ const trimmedLogs = bufferSizeLimit > -1 ? combinedLogs.slice(-bufferSizeLimit) : combinedLogs;
44
+
45
+ fs.writeFileSync(logFilePath, `${trimmedLogs.join('\n')}\n`);
46
+
47
+ logBuffer = [];
48
+ }
49
+ } catch (err) {
50
+ console.error('Failed to flush:', err);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Logs a message to the console and a log file.
56
+ * @param message
57
+ * @param level
58
+ * @param optionalParams
59
+ */
60
+ function log(message?: string, level: LogLevel = LogLevel.INFO, ...optionalParams: unknown[]): void {
61
+ const timestamp = new Date().toISOString();
62
+ const origin = getCallerInfo();
63
+ const formatted = `[${timestamp}] [${level}] [${origin}] ${message}`;
64
+
65
+ switch (level) {
66
+ case LogLevel.FATAL:
67
+ case LogLevel.ERROR:
68
+ originalError(formatted, ...optionalParams);
69
+ break;
70
+ case LogLevel.WARN:
71
+ originalWarn(formatted, ...optionalParams);
72
+ break;
73
+ case LogLevel.DEBUG:
74
+ originalDebug(formatted, ...optionalParams);
75
+ break;
76
+ case LogLevel.INFO:
77
+ originalInfo(formatted, ...optionalParams);
78
+ break;
79
+ default:
80
+ originalLog(formatted, ...optionalParams);
81
+ break;
82
+ }
83
+
84
+ logBuffer.push(`${formatted} ${optionalParams}`);
85
+ flushLogs();
86
+ }
87
+
88
+ /**
89
+ * Retrieves the caller information from the stack trace.
90
+ * @returns {string} The caller information in the format "file:line:column"
91
+ */
92
+ function getCallerInfo(): string {
93
+ const stack = new Error().stack;
94
+ if (!stack) return 'unknown origin';
95
+
96
+ const stackLines = stack.split('\n');
97
+
98
+ for (const line of stackLines) {
99
+ if (!line.includes('Logger.ts') && !line.includes('index.ts')) {
100
+ const match = line.match(/\((.*):(\d+):(\d+)\)/);
101
+ if (match) {
102
+ let file = match[1];
103
+ const lineNumber = match[2];
104
+ const columnNumber = match[3];
105
+
106
+ if (file.startsWith('file:///')) {
107
+ file = decodeURI(file.replace('file:///', ''));
108
+ }
109
+ return `${file}:${lineNumber}:${columnNumber}`;
110
+ }
111
+ }
112
+ }
113
+
114
+ return 'unknown origin';
115
+ }
116
+
117
+ export const logger = {
118
+ // Order by severity level
119
+ fatal: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.FATAL, ...optionalParams),
120
+ error: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.ERROR, ...optionalParams),
121
+ warn: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.WARN, ...optionalParams),
122
+ step: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.STEP, ...optionalParams),
123
+ info: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.INFO, ...optionalParams),
124
+ debug: (msg?: string, ...optionalParams: unknown[]): void => log(msg, LogLevel.DEBUG, ...optionalParams)
125
+ };
@@ -0,0 +1,41 @@
1
+ // This is tightly coupled to the Testify reporting service integration and is subject to change.
2
+
3
+ import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
4
+ import { TestifyService } from '../example-code/TestifyService.js';
5
+
6
+ interface StoredTestResult {
7
+ title: string;
8
+ status: TestResult['status'];
9
+ }
10
+
11
+ class ReportingService implements Reporter {
12
+ private _testifyService: TestifyService | null = null;
13
+ private _results: StoredTestResult[] = [];
14
+
15
+ onTestEnd(test: TestCase, result: TestResult) {
16
+ console.info(`Test ended: ${test.title}, status: ${result.status}`);
17
+
18
+ if (process.env.REPORTING_ENABLED !== 'true') return;
19
+
20
+ this._results.push({ title: test.title, status: result.status });
21
+ }
22
+
23
+ async onEnd(result: FullResult) {
24
+ if (process.env.REPORTING_ENABLED !== 'true') return;
25
+
26
+ this._testifyService = new TestifyService();
27
+
28
+ await this._testifyService.createTestRun();
29
+
30
+ if (this._testifyService) {
31
+ for (const testResult of this._results) {
32
+ await this._testifyService.reportTestResult(testResult.title, testResult.status === 'passed');
33
+ }
34
+ }
35
+
36
+ if (result.status === 'passed' && process.env.AUTO_CLOSE_PASSING_RUNS === 'true')
37
+ await this._testifyService?.closeTestRun();
38
+ }
39
+ }
40
+
41
+ export default ReportingService;
@@ -0,0 +1,9 @@
1
+ import type {
2
+ PlaywrightTestArgs,
3
+ PlaywrightTestOptions,
4
+ PlaywrightWorkerArgs,
5
+ PlaywrightWorkerOptions,
6
+ TestType
7
+ } from '@playwright/test';
8
+
9
+ export type Test = TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
@@ -0,0 +1,26 @@
1
+ export type UniversalConfigOptions = {
2
+ barcodeScannerSymbologies?: string;
3
+ cortexEnabledSymbologies?: string[];
4
+ cortexLicense?: string;
5
+ debugMode?: boolean;
6
+ devDisableProcessFlows?: boolean;
7
+ useFormFieldBackgroundColor?: boolean;
8
+ defaultApplication?: string;
9
+ defaultPlatform?: number;
10
+ loginOptionsDisabled?: boolean;
11
+ loginOptionsHidden?: boolean;
12
+ installNotificationDisabled?: boolean;
13
+ enableDragDrop?: boolean;
14
+ installNotificationExpirationInDays?: number;
15
+ spacingMode?: string;
16
+ serviceUrl?: string;
17
+ useServiceWorker?: boolean;
18
+ };
19
+
20
+ export type UniversalLoginOptions = {
21
+ username?: string;
22
+ password?: string;
23
+ serviceUrl?: string;
24
+ metaEndpoint?: string;
25
+ config?: UniversalConfigOptions;
26
+ };