@playq/core 0.2.77

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 (225) hide show
  1. package/README.md +41 -0
  2. package/bin/playq.js +175 -0
  3. package/cucumber.js +10 -0
  4. package/dist/exec/featureFileCache.d.ts +21 -0
  5. package/dist/exec/featureFileCache.js +124 -0
  6. package/dist/exec/featureFilePreProcess.d.ts +12 -0
  7. package/dist/exec/featureFilePreProcess.js +208 -0
  8. package/dist/exec/preLoader.d.ts +1 -0
  9. package/dist/exec/preLoader.js +72 -0
  10. package/dist/exec/preProcessEntry.d.ts +1 -0
  11. package/dist/exec/preProcessEntry.js +83 -0
  12. package/dist/exec/preProcess_old_todelete.d.ts +1 -0
  13. package/dist/exec/preProcess_old_todelete.js +258 -0
  14. package/dist/exec/runner.d.ts +1 -0
  15. package/dist/exec/runner.js +221 -0
  16. package/dist/exec/runner_orchestrator.d.ts +1 -0
  17. package/dist/exec/runner_orchestrator.js +85 -0
  18. package/dist/exec/sgGenerator.d.ts +11 -0
  19. package/dist/exec/sgGenerator.js +310 -0
  20. package/dist/global.d.ts +15 -0
  21. package/dist/global.js +185 -0
  22. package/dist/helper/actions/api/apiRequestActions.d.ts +117 -0
  23. package/dist/helper/actions/api/apiRequestActions.js +374 -0
  24. package/dist/helper/actions/api/apiValidationActions.d.ts +119 -0
  25. package/dist/helper/actions/api/apiValidationActions.js +615 -0
  26. package/dist/helper/actions/apiActions.d.ts +18 -0
  27. package/dist/helper/actions/apiActions.js +34 -0
  28. package/dist/helper/actions/apiStepDefs.d.ts +1 -0
  29. package/dist/helper/actions/apiStepDefs.js +64 -0
  30. package/dist/helper/actions/comm/commonActions.d.ts +58 -0
  31. package/dist/helper/actions/comm/commonActions.js +198 -0
  32. package/dist/helper/actions/comm/utilityActions.d.ts +131 -0
  33. package/dist/helper/actions/comm/utilityActions.js +351 -0
  34. package/dist/helper/actions/commActions.d.ts +18 -0
  35. package/dist/helper/actions/commActions.js +34 -0
  36. package/dist/helper/actions/commStepDefs.d.ts +1 -0
  37. package/dist/helper/actions/commStepDefs.js +57 -0
  38. package/dist/helper/actions/stepGroupStepDefs.d.ts +1 -0
  39. package/dist/helper/actions/stepGroupStepDefs.js +15 -0
  40. package/dist/helper/actions/web/alertActions.d.ts +61 -0
  41. package/dist/helper/actions/web/alertActions.js +224 -0
  42. package/dist/helper/actions/web/cookieActions.d.ts +45 -0
  43. package/dist/helper/actions/web/cookieActions.js +186 -0
  44. package/dist/helper/actions/web/downloadActions.d.ts +40 -0
  45. package/dist/helper/actions/web/downloadActions.js +153 -0
  46. package/dist/helper/actions/web/elementReaderActions.d.ts +95 -0
  47. package/dist/helper/actions/web/elementReaderActions.js +326 -0
  48. package/dist/helper/actions/web/formActions.d.ts +122 -0
  49. package/dist/helper/actions/web/formActions.js +423 -0
  50. package/dist/helper/actions/web/iframeActions.d.ts +23 -0
  51. package/dist/helper/actions/web/iframeActions.js +108 -0
  52. package/dist/helper/actions/web/javascriptActions.d.ts +14 -0
  53. package/dist/helper/actions/web/javascriptActions.js +77 -0
  54. package/dist/helper/actions/web/keyboardActions.d.ts +35 -0
  55. package/dist/helper/actions/web/keyboardActions.js +118 -0
  56. package/dist/helper/actions/web/localStorageActions.d.ts +51 -0
  57. package/dist/helper/actions/web/localStorageActions.js +163 -0
  58. package/dist/helper/actions/web/mouseActions.d.ts +240 -0
  59. package/dist/helper/actions/web/mouseActions.js +609 -0
  60. package/dist/helper/actions/web/reportingActions.d.ts +34 -0
  61. package/dist/helper/actions/web/reportingActions.js +58 -0
  62. package/dist/helper/actions/web/screenshotActions.d.ts +34 -0
  63. package/dist/helper/actions/web/screenshotActions.js +151 -0
  64. package/dist/helper/actions/web/testDataActions.d.ts +21 -0
  65. package/dist/helper/actions/web/testDataActions.js +211 -0
  66. package/dist/helper/actions/web/validationActions.d.ts +547 -0
  67. package/dist/helper/actions/web/validationActions.js +1754 -0
  68. package/dist/helper/actions/web/waitActions.d.ts +191 -0
  69. package/dist/helper/actions/web/waitActions.js +589 -0
  70. package/dist/helper/actions/web/webNavigation.d.ts +104 -0
  71. package/dist/helper/actions/web/webNavigation.js +288 -0
  72. package/dist/helper/actions/webActions.d.ts +32 -0
  73. package/dist/helper/actions/webActions.js +48 -0
  74. package/dist/helper/actions/webStepDefs.d.ts +1 -0
  75. package/dist/helper/actions/webStepDefs.js +455 -0
  76. package/dist/helper/browsers/browserManager.d.ts +1 -0
  77. package/dist/helper/browsers/browserManager.js +56 -0
  78. package/dist/helper/bundle/defaultEntries.d.ts +6 -0
  79. package/dist/helper/bundle/defaultEntries.js +200 -0
  80. package/dist/helper/bundle/env.d.ts +1 -0
  81. package/dist/helper/bundle/env.js +157 -0
  82. package/dist/helper/bundle/vars.d.ts +9 -0
  83. package/dist/helper/bundle/vars.js +375 -0
  84. package/dist/helper/faker/customFaker.d.ts +55 -0
  85. package/dist/helper/faker/customFaker.js +45 -0
  86. package/dist/helper/faker/modules/data/postcodes_valid_sg.json +17 -0
  87. package/dist/helper/faker/modules/dateTime.d.ts +18 -0
  88. package/dist/helper/faker/modules/dateTime.js +106 -0
  89. package/dist/helper/faker/modules/mobile.d.ts +4 -0
  90. package/dist/helper/faker/modules/mobile.js +59 -0
  91. package/dist/helper/faker/modules/nric.d.ts +32 -0
  92. package/dist/helper/faker/modules/nric.js +84 -0
  93. package/dist/helper/faker/modules/passport.d.ts +3 -0
  94. package/dist/helper/faker/modules/passport.js +36 -0
  95. package/dist/helper/faker/modules/person.d.ts +14 -0
  96. package/dist/helper/faker/modules/person.js +73 -0
  97. package/dist/helper/faker/modules/postcode.d.ts +6 -0
  98. package/dist/helper/faker/modules/postcode.js +47 -0
  99. package/dist/helper/fixtures/locAggregate.d.ts +7 -0
  100. package/dist/helper/fixtures/locAggregate.js +94 -0
  101. package/dist/helper/fixtures/logFixture.d.ts +8 -0
  102. package/dist/helper/fixtures/logFixture.js +56 -0
  103. package/dist/helper/fixtures/webFixture.d.ts +19 -0
  104. package/dist/helper/fixtures/webFixture.js +186 -0
  105. package/dist/helper/fixtures/webLocFixture.d.ts +2 -0
  106. package/dist/helper/fixtures/webLocFixture.js +144 -0
  107. package/dist/helper/report/allureStepLogger.d.ts +0 -0
  108. package/dist/helper/report/allureStepLogger.js +25 -0
  109. package/dist/helper/report/customiseReport.d.ts +1 -0
  110. package/dist/helper/report/customiseReport.js +55 -0
  111. package/dist/helper/report/init.d.ts +1 -0
  112. package/dist/helper/report/init.js +14 -0
  113. package/dist/helper/report/report.d.ts +1 -0
  114. package/dist/helper/report/report.js +102 -0
  115. package/dist/helper/util/dataLoader.d.ts +10 -0
  116. package/dist/helper/util/dataLoader.js +73 -0
  117. package/dist/helper/util/logger.d.ts +4 -0
  118. package/dist/helper/util/logger.js +61 -0
  119. package/dist/helper/util/session/sessionUtil.d.ts +26 -0
  120. package/dist/helper/util/session/sessionUtil.js +729 -0
  121. package/dist/helper/util/stepHelpers.d.ts +2 -0
  122. package/dist/helper/util/stepHelpers.js +16 -0
  123. package/dist/helper/util/test-data/dataLoader.d.ts +7 -0
  124. package/dist/helper/util/test-data/dataLoader.js +145 -0
  125. package/dist/helper/util/test-data/dataTest.d.ts +10 -0
  126. package/dist/helper/util/test-data/dataTest.js +216 -0
  127. package/dist/helper/util/totp/totpHelper.d.ts +38 -0
  128. package/dist/helper/util/totp/totpHelper.js +117 -0
  129. package/dist/helper/util/utilities/cryptoUtil.d.ts +2 -0
  130. package/dist/helper/util/utilities/cryptoUtil.js +53 -0
  131. package/dist/helper/util/utilities/schemaGeneratorUtil.d.ts +2 -0
  132. package/dist/helper/util/utilities/schemaGeneratorUtil.js +129 -0
  133. package/dist/helper/util/utils.d.ts +2 -0
  134. package/dist/helper/util/utils.js +22 -0
  135. package/dist/helper/wrapper/PlaywrightWrappers.d.ts +8 -0
  136. package/dist/helper/wrapper/PlaywrightWrappers.js +26 -0
  137. package/dist/helper/wrapper/assert.d.ts +9 -0
  138. package/dist/helper/wrapper/assert.js +23 -0
  139. package/dist/index.d.ts +7 -0
  140. package/dist/index.js +57 -0
  141. package/dist/scripts/get-versions.d.ts +1 -0
  142. package/dist/scripts/get-versions.js +98 -0
  143. package/dist/scripts/posttest.d.ts +1 -0
  144. package/dist/scripts/posttest.js +29 -0
  145. package/dist/scripts/pretest.d.ts +1 -0
  146. package/dist/scripts/pretest.js +57 -0
  147. package/dist/scripts/util.d.ts +1 -0
  148. package/dist/scripts/util.js +376 -0
  149. package/package.json +68 -0
  150. package/src/exec/featureFileCache.ts +80 -0
  151. package/src/exec/featureFilePreProcess.ts +239 -0
  152. package/src/exec/preLoader.ts +72 -0
  153. package/src/exec/preProcessEntry.ts +59 -0
  154. package/src/exec/preProcess_old_todelete.ts +289 -0
  155. package/src/exec/runner.ts +241 -0
  156. package/src/exec/runnerCuke.js +90 -0
  157. package/src/exec/runner_orchestrator.ts +91 -0
  158. package/src/exec/sgGenerator.ts +373 -0
  159. package/src/global.ts +130 -0
  160. package/src/helper/actions/api/apiRequestActions.ts +362 -0
  161. package/src/helper/actions/api/apiValidationActions.ts +594 -0
  162. package/src/helper/actions/apiActions.ts +18 -0
  163. package/src/helper/actions/apiStepDefs.ts +80 -0
  164. package/src/helper/actions/comm/commonActions.ts +165 -0
  165. package/src/helper/actions/comm/utilityActions.ts +344 -0
  166. package/src/helper/actions/commActions.ts +18 -0
  167. package/src/helper/actions/commStepDefs.ts +72 -0
  168. package/src/helper/actions/stepGroupStepDefs.ts +17 -0
  169. package/src/helper/actions/web/alertActions.ts +179 -0
  170. package/src/helper/actions/web/cookieActions.ts +124 -0
  171. package/src/helper/actions/web/downloadActions.ts +129 -0
  172. package/src/helper/actions/web/elementReaderActions.ts +323 -0
  173. package/src/helper/actions/web/formActions.ts +469 -0
  174. package/src/helper/actions/web/iframeActions.ts +67 -0
  175. package/src/helper/actions/web/javascriptActions.ts +38 -0
  176. package/src/helper/actions/web/keyboardActions.ts +101 -0
  177. package/src/helper/actions/web/localStorageActions.ts +109 -0
  178. package/src/helper/actions/web/mouseActions.ts +864 -0
  179. package/src/helper/actions/web/reportingActions.ts +53 -0
  180. package/src/helper/actions/web/screenshotActions.ts +124 -0
  181. package/src/helper/actions/web/testDataActions.ts +162 -0
  182. package/src/helper/actions/web/validationActions.ts +2287 -0
  183. package/src/helper/actions/web/waitActions.ts +757 -0
  184. package/src/helper/actions/web/webNavigation.ts +313 -0
  185. package/src/helper/actions/webActions.ts +33 -0
  186. package/src/helper/actions/webStepDefs.ts +505 -0
  187. package/src/helper/browsers/browserManager.ts +23 -0
  188. package/src/helper/bundle/defaultEntries.ts +208 -0
  189. package/src/helper/bundle/env.ts +119 -0
  190. package/src/helper/bundle/vars.ts +368 -0
  191. package/src/helper/faker/customFaker.ts +107 -0
  192. package/src/helper/faker/modules/data/postcodes_valid_sg.json +17 -0
  193. package/src/helper/faker/modules/dateTime.ts +121 -0
  194. package/src/helper/faker/modules/mobile.ts +58 -0
  195. package/src/helper/faker/modules/nric.ts +109 -0
  196. package/src/helper/faker/modules/passport.ts +34 -0
  197. package/src/helper/faker/modules/person.ts +93 -0
  198. package/src/helper/faker/modules/postcode.ts +57 -0
  199. package/src/helper/fixtures/locAggregate.ts +61 -0
  200. package/src/helper/fixtures/logFixture.ts +57 -0
  201. package/src/helper/fixtures/webFixture.ts +206 -0
  202. package/src/helper/fixtures/webLocFixture.ts +143 -0
  203. package/src/helper/report/allureStepLogger.ts +26 -0
  204. package/src/helper/report/customiseReport.ts +61 -0
  205. package/src/helper/report/init.ts +18 -0
  206. package/src/helper/report/report.ts +72 -0
  207. package/src/helper/util/dataLoader.ts +42 -0
  208. package/src/helper/util/logger.ts +32 -0
  209. package/src/helper/util/session/sessionUtil.ts +839 -0
  210. package/src/helper/util/stepHelpers.ts +14 -0
  211. package/src/helper/util/test-data/dataLoader.ts +108 -0
  212. package/src/helper/util/test-data/dataTest.ts +191 -0
  213. package/src/helper/util/test-data/registerUser.json +7 -0
  214. package/src/helper/util/totp/totpHelper.ts +102 -0
  215. package/src/helper/util/utilities/cryptoUtil.ts +53 -0
  216. package/src/helper/util/utilities/schemaGeneratorUtil.ts +143 -0
  217. package/src/helper/util/utils.ts +28 -0
  218. package/src/helper/wrapper/PlaywrightWrappers.ts +28 -0
  219. package/src/helper/wrapper/assert.ts +25 -0
  220. package/src/index.ts +17 -0
  221. package/src/scripts/get-versions.ts +68 -0
  222. package/src/scripts/posttest.ts +32 -0
  223. package/src/scripts/pretest.ts +48 -0
  224. package/src/scripts/util.ts +406 -0
  225. package/tsconfig.json +30 -0
@@ -0,0 +1,14 @@
1
+ import { vars } from '../../global';
2
+
3
+ export async function attachResolvedStep(thisArg: any, template: string) {
4
+ const resolved = vars.replaceVariables(template);
5
+ await thisArg.attach(`🧾 Resolved Step: ${resolved}`, 'text/plain');
6
+ }
7
+
8
+ export function defineLoggedStep(pattern: string, implementation: Function) {
9
+ return function (this: any, ...args: any[]) {
10
+ const stepText = pattern.replace(/\{param\}/g, (_match, i) => `"${args[i]}"`);
11
+ attachResolvedStep.call(this, stepText);
12
+ return implementation.apply(this, args);
13
+ };
14
+ }
@@ -0,0 +1,108 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import * as XLSX from '@e965/xlsx';
4
+
5
+ /**
6
+ * Reads test data from .json, .xlsx, or .csv
7
+ *
8
+ * @param file - Filename WITH extension, e.g., "login.json", "login.xlsx", "login.csv"
9
+ * @param sheetName - (optional) Sheet name for Excel files
10
+ */
11
+ export function getTestData(file: string, sheetName?: string): any[] {
12
+ // Attempt to locate the project's test-data directory robustly.
13
+ // Strategy:
14
+ // 1. Starting at process.cwd(), walk upward looking for a folder that
15
+ // contains a package.json (project root) and a test-data subfolder.
16
+ // 2. If found, use <foundRoot>/test-data/<file>.
17
+ // 3. Otherwise, fallback to process.cwd()/test-data/<file> and report a clear ENOENT.
18
+
19
+ function findProjectRootWithTestData(startDir: string): string | null {
20
+ let current = path.resolve(startDir);
21
+ const root = path.parse(current).root;
22
+ while (true) {
23
+ const pkg = path.join(current, 'package.json');
24
+ const td = path.join(current, 'test-data');
25
+ if (fs.existsSync(pkg) && fs.existsSync(td) && fs.statSync(td).isDirectory()) {
26
+ return td;
27
+ }
28
+ if (current === root) break;
29
+ current = path.dirname(current);
30
+ }
31
+ return null;
32
+ }
33
+
34
+ const start = process.cwd();
35
+ const projectTestData = findProjectRootWithTestData(start);
36
+
37
+ // If not found by walking upward, also check immediate subdirectories of the
38
+ // current directory. This handles the common developer workspace layout where
39
+ // the workspace root contains multiple project folders (e.g. PlayQ_PROJECT).
40
+ let basePath: string;
41
+ if (projectTestData) {
42
+ basePath = projectTestData;
43
+ } else {
44
+ // scan one-level children for a project that contains package.json and test-data
45
+ const children = fs.readdirSync(start, { withFileTypes: true });
46
+ let found: string | null = null;
47
+ for (const child of children) {
48
+ if (!child.isDirectory()) continue;
49
+ const candidate = path.join(start, child.name);
50
+ const pkg = path.join(candidate, 'package.json');
51
+ const td = path.join(candidate, 'test-data');
52
+ try {
53
+ if (fs.existsSync(pkg) && fs.existsSync(td) && fs.statSync(td).isDirectory()) {
54
+ found = td;
55
+ break;
56
+ }
57
+ } catch (e) {
58
+ // ignore permission or stat errors on non-readable dirs
59
+ }
60
+ }
61
+ basePath = found || path.resolve(process.cwd(), 'test-data');
62
+ }
63
+
64
+ const filePath = path.join(basePath, file);
65
+
66
+ if (!fs.existsSync(filePath)) {
67
+ throw new Error(`ENOENT: test data file not found: ${filePath}. Searched from ${start}`);
68
+ }
69
+ const ext = path.extname(file).toLowerCase();
70
+
71
+ switch (ext) {
72
+ case '.json': {
73
+ const raw = fs.readFileSync(filePath, 'utf-8');
74
+ return JSON.parse(raw);
75
+ }
76
+ case '.xlsx': {
77
+ const workbook = XLSX.readFile(filePath);
78
+ const sheet = workbook.Sheets[sheetName || workbook.SheetNames[0]];
79
+ const rows = XLSX.utils.sheet_to_json<Record<string, any>>(sheet, { raw: false });
80
+
81
+ // Try to parse booleans/numbers
82
+ return rows.map(row => {
83
+ const parsedRow: Record<string, any> = {};
84
+ for (const key in row) {
85
+ const value = row[key];
86
+ if (value === 'true' || value === 'false') {
87
+ parsedRow[key] = value === 'true';
88
+ } else if (!isNaN(value) && value.trim() !== '') {
89
+ parsedRow[key] = Number(value);
90
+ } else {
91
+ parsedRow[key] = value;
92
+ }
93
+ }
94
+ return parsedRow;
95
+ });
96
+ // const workbook = XLSX.readFile(filePath);
97
+ // const sheet = workbook.Sheets[sheetName || workbook.SheetNames[0]];
98
+ // return XLSX.utils.sheet_to_json(sheet);
99
+ }
100
+ case '.csv': {
101
+ const fileData = fs.readFileSync(filePath, 'utf-8');
102
+ const worksheet = XLSX.read(fileData, { type: 'string' }).Sheets['Sheet1'];
103
+ return XLSX.utils.sheet_to_json(worksheet);
104
+ }
105
+ default:
106
+ throw new Error(`Unsupported file extension: ${ext}`);
107
+ }
108
+ }
@@ -0,0 +1,191 @@
1
+ // dataTest.ts
2
+ import { test } from '@playwright/test';
3
+ import { getTestData } from './dataLoader';
4
+ import * as vars from '../../bundle/vars';
5
+ import { faker as coreFaker } from '../../faker/customFaker';
6
+
7
+ type DataSource<T> = T[] | {
8
+ file: string;
9
+ filter?: string;
10
+ sheet?: string;
11
+ testType?: string;
12
+ suffix?: string; // Optional suffix for test name
13
+ };
14
+
15
+ export function dataTest<T extends Record<string, any>>(
16
+ label: string,
17
+ dataSource: T[] | { file: string; filter?: string; sheet?: string ; testType?: string; suffix?: string },
18
+ callback: (args: { row: T; page?: any }) => Promise<void>
19
+ ) {
20
+ let dataset: T[] = [];
21
+ const { testType = "UI", suffix = ""} = typeof dataSource === "object" && !Array.isArray(dataSource) ? dataSource : {};
22
+
23
+ if (Array.isArray(dataSource)) {
24
+ dataset = applyRowReplacements(dataSource as any);
25
+ } else {
26
+ let { file, filter, sheet } = dataSource;
27
+ file = vars.replaceVariables(file);
28
+ sheet = (sheet) ? vars.replaceVariables(sheet) : undefined;
29
+ filter = vars.replaceVariables(filter);
30
+ const raw = getTestData(file, sheet);
31
+ // Expand faker/variable placeholders within each row before filtering/scheduling tests
32
+ const preprocessed = applyRowReplacements(raw as any);
33
+ dataset = filter
34
+ ? (preprocessed as T[]).filter(row => {
35
+ try {
36
+ // Create a scoped function where row keys are destructured
37
+ const fn = new Function("row", `
38
+ const { ${Object.keys(row).join(", ")} } = row;
39
+ return ${filter};
40
+ `);
41
+ return fn(row);
42
+ } catch (err) {
43
+ console.warn(`⚠️ Filter failed: ${filter}`, err);
44
+ return false;
45
+ }
46
+ })
47
+ : (preprocessed as T[]);
48
+ }
49
+
50
+ test.describe(label, () => {
51
+ dataset.forEach((row, index) => {
52
+ vars.setValue('playq.iteration.count',`${index + 1}`);
53
+ const name = `${label} ${vars.replaceVariables(replaceIterationDataVars(suffix,row))} [-${index + 1}-]`;
54
+ if (testType.toUpperCase() === "API") {
55
+ test(name, async () => {
56
+ test.info().annotations.push({ type: "tag", description: label });
57
+ await callback({ row });
58
+ });
59
+ } else {
60
+ test(name, async ({ page }) => {
61
+ test.info().annotations.push({ type: "tag", description: label });
62
+ await callback({ row, page });
63
+ });
64
+ }
65
+ });
66
+ });
67
+ }
68
+
69
+ // ----------------- helpers -----------------
70
+ function applyRowReplacements<T extends Record<string, any>>(rows: T[]): T[] {
71
+ return rows.map((row) => {
72
+ const out: Record<string, any> = { ...row };
73
+ for (const k of Object.keys(out)) {
74
+ const v = out[k];
75
+ if (typeof v === 'string') {
76
+ // First, evaluate any faker placeholders
77
+ const withFaker = replaceFakerPlaceholders(v);
78
+ // Then, apply generic variable replacements
79
+ out[k] = typeof withFaker === 'string' ? vars.replaceVariables(withFaker) : withFaker;
80
+ }
81
+ }
82
+ return out as T;
83
+ });
84
+ }
85
+
86
+ function replaceFakerPlaceholders(input: string): any {
87
+ const trimmed = input.trim();
88
+ // Entire value is wrapped: #{faker....}
89
+ const wrapped = trimmed.match(/^#\{(faker(?:\.[a-zA-Z0-9_]+)+\((.*)\))\}$/);
90
+ if (wrapped) {
91
+ return evalFakerCall(wrapped[1]);
92
+ }
93
+ // Entire value is a faker call: faker....
94
+ const full = trimmed.match(/^faker((?:\.[a-zA-Z0-9_]+)+)\((.*)\)$/);
95
+ if (full) {
96
+ return evalFakerFromParts(full[1], full[2]);
97
+ }
98
+ // Embedded placeholders inside a larger string
99
+ return input.replace(/#\{faker((?:\.[a-zA-Z0-9_]+)+)\((.*?)\)\}/g, (_m, pathPart, argsRaw) => {
100
+ try {
101
+ const val = evalFakerFromParts(pathPart, argsRaw);
102
+ return String(val);
103
+ } catch (e) {
104
+ console.warn(`⚠️ Failed to evaluate faker placeholder: #{faker${pathPart}(${argsRaw})}`, e);
105
+ return _m;
106
+ }
107
+ });
108
+ }
109
+
110
+ function evalFakerCall(expr: string): any {
111
+ // expr like: faker.xxx.yyy(args)
112
+ const m = expr.match(/^faker((?:\.[a-zA-Z0-9_]+)+)\((.*)\)$/);
113
+ if (!m) throw new Error(`Invalid faker expression: ${expr}`);
114
+ return evalFakerFromParts(m[1], m[2]);
115
+ }
116
+
117
+ function evalFakerFromParts(pathPart: string, argsRaw: string): any {
118
+ const path = pathPart.replace(/^\./, '');
119
+ const parts = path.split('.');
120
+ // Prefer global faker set up by core; fall back to imported one
121
+ let ctx: any = (globalThis as any).faker || coreFaker;
122
+ let fn: any = ctx;
123
+ for (const p of parts) {
124
+ fn = fn?.[p];
125
+ }
126
+ if (typeof fn !== 'function') throw new Error(`Resolved faker path is not a function: faker.${path}`);
127
+ const args = parseFakerArgs(argsRaw);
128
+ return fn(...args);
129
+ }
130
+
131
+ function parseFakerArgs(argsRaw: string): any[] {
132
+ const trimmed = (argsRaw || '').trim();
133
+ if (!trimmed) return [];
134
+ if (trimmed.startsWith('{')) {
135
+ // Object literal; normalize to JSON
136
+ const normalized = trimmed
137
+ .replace(/([{,]\s*)([a-zA-Z0-9_]+)\s*:/g, '$1"$2":')
138
+ .replace(/'/g, '"');
139
+ try {
140
+ return [JSON.parse(normalized)];
141
+ } catch (e) {
142
+ throw new Error(`Failed to parse faker argument object: ${argsRaw}`);
143
+ }
144
+ }
145
+ // Simple comma-separated values (strip surrounding quotes)
146
+ return splitArgs(trimmed).map(a => a.trim().replace(/^(["'])(.*)\1$/, '$2'));
147
+ }
148
+
149
+ function splitArgs(s: string): string[] {
150
+ const out: string[] = [];
151
+ let buf = '';
152
+ let depth = 0;
153
+ let quote: '"' | "'" | null = null;
154
+ for (let i = 0; i < s.length; i++) {
155
+ const ch = s[i];
156
+ if (quote) {
157
+ if (ch === quote && s[i - 1] !== '\\') {
158
+ quote = null;
159
+ }
160
+ buf += ch;
161
+ continue;
162
+ }
163
+ if (ch === '"' || ch === "'") {
164
+ quote = ch as any;
165
+ buf += ch;
166
+ continue;
167
+ }
168
+ if (ch === '{' || ch === '[' || ch === '(') depth++;
169
+ if (ch === '}' || ch === ']' || ch === ')') depth--;
170
+ if (ch === ',' && depth === 0) {
171
+ out.push(buf);
172
+ buf = '';
173
+ continue;
174
+ }
175
+ buf += ch;
176
+ }
177
+ if (buf) out.push(buf);
178
+ return out;
179
+ }
180
+
181
+ /**
182
+ * Replaces all #{playq.iteration.data.KEY} in the input string with the value from the row object.
183
+ * @param input The string containing placeholders.
184
+ * @param row The row object with data.
185
+ * @returns The string with placeholders replaced.
186
+ */
187
+ function replaceIterationDataVars(input: string, row: Record<string, any>): string {
188
+ return input.replace(/#\{playq\.iteration\.data\.([a-zA-Z0-9_]+)\}/g, (_, key) => {
189
+ return row[key] !== undefined ? String(row[key]) : '';
190
+ });
191
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "firstName": "Koushik ",
3
+ "lastName": "C ",
4
+ "userName": "user",
5
+ "password": "Pass123$",
6
+ "confirmPassword": "Pass123$"
7
+ }
@@ -0,0 +1,102 @@
1
+ import { authenticator } from 'otplib';
2
+ import * as QRCode from 'qrcode';
3
+
4
+ export class TOTPHelper {
5
+ private secret: string;
6
+ private serviceName: string;
7
+ private accountName: string;
8
+
9
+ constructor(secret?: string, serviceName: string = 'PlayQ', accountName: string = 'automation') {
10
+ this.secret = secret || this.generateSecret();
11
+ this.serviceName = serviceName;
12
+ this.accountName = accountName;
13
+
14
+ // Configure otplib options
15
+ authenticator.options = {
16
+ window: 2, // Allow 2 time steps tolerance
17
+ step: 30 // 30-second time step
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Generate a new TOTP secret
23
+ */
24
+ generateSecret(): string {
25
+ return authenticator.generateSecret();
26
+ }
27
+
28
+ /**
29
+ * Generate current TOTP token
30
+ */
31
+ generateToken(): string {
32
+ return authenticator.generate(this.secret);
33
+ }
34
+
35
+ /**
36
+ * Verify TOTP token
37
+ */
38
+ verifyToken(token: string): boolean {
39
+ return authenticator.verify({
40
+ token,
41
+ secret: this.secret
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Get OTP Auth URL for QR code generation
47
+ */
48
+ getOtpAuthUrl(): string {
49
+ return authenticator.keyuri(
50
+ this.accountName,
51
+ this.serviceName,
52
+ this.secret
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Generate QR code for authenticator app setup
58
+ */
59
+ async generateQRCode(): Promise<string> {
60
+ const otpAuthUrl = this.getOtpAuthUrl();
61
+ return await QRCode.toDataURL(otpAuthUrl);
62
+ }
63
+
64
+ /**
65
+ * Get the secret (for environment variable storage)
66
+ */
67
+ getSecret(): string {
68
+ return this.secret;
69
+ }
70
+
71
+ /**
72
+ * Check if current time is near token expiry
73
+ */
74
+ getTimeRemaining(): number {
75
+ const epoch = Math.round(Date.now() / 1000.0);
76
+ const timeStep = 30;
77
+ return timeStep - (epoch % timeStep);
78
+ }
79
+
80
+ /**
81
+ * Generate token with retry logic for time window
82
+ */
83
+ async generateTokenWithRetry(maxRetries: number = 3): Promise<string> {
84
+ let attempts = 0;
85
+
86
+ while (attempts < maxRetries) {
87
+ const timeRemaining = this.getTimeRemaining();
88
+
89
+ // If token expires soon, wait for new window
90
+ if (timeRemaining < 5) {
91
+ console.log(`Token expires in ${timeRemaining}s, waiting for new window...`);
92
+ await new Promise(resolve => setTimeout(resolve, (timeRemaining + 1) * 1000));
93
+ }
94
+
95
+ const token = this.generateToken();
96
+ console.log(`Generated TOTP token: ${token.substring(0, 3)}*** (${this.getTimeRemaining()}s remaining)`);
97
+ return token;
98
+ }
99
+
100
+ throw new Error('Failed to generate TOTP token after retries');
101
+ }
102
+ }
@@ -0,0 +1,53 @@
1
+ // src/utils/cryptoUtil.ts
2
+ import crypto from 'crypto';
3
+ import dotenv from 'dotenv';
4
+
5
+ dotenv.config();
6
+ const algorithm = 'aes-256-gcm';
7
+ const secretKey = crypto.scryptSync(process.env.PLAYQ_SECRET_KEY || '_DEFAULT_SECRET_32_CHARACTERS_!_', 'salt', 32);
8
+ const ivLength = 12; // Recommended IV size for GCM
9
+
10
+ export function encrypt(plaintext: string): string {
11
+ const iv = crypto.randomBytes(ivLength);
12
+ const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
13
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
14
+ const authTag = cipher.getAuthTag();
15
+ const combined = Buffer.concat([iv, authTag, encrypted]);
16
+ return combined.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
17
+ }
18
+
19
+ export function decrypt(data: string): string {
20
+ const combined = Buffer.from(data.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
21
+ const iv = combined.subarray(0, ivLength);
22
+ const authTag = combined.subarray(ivLength, ivLength + 16);
23
+ const encrypted = combined.subarray(ivLength + 16);
24
+ const decipher = crypto.createDecipheriv(algorithm, secretKey, iv);
25
+ decipher.setAuthTag(authTag);
26
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
27
+ return decrypted.toString('utf8');
28
+ }
29
+
30
+ // const algorithm = 'aes-256-cbc';
31
+ // const secretKey = process.env.CRYPTO_SECRET_KEY || '_DEFAULT_SECRET_32_CHARACTERS_!_';
32
+ // const ivLength = 16; // AES block size
33
+
34
+ // // --- Encrypt ---
35
+ // export function encrypt(text: string): string {
36
+ // const iv = crypto.randomBytes(ivLength);
37
+ // const cipher = crypto.createCipheriv(algorithm, Buffer.from(secretKey, 'utf-8'), iv);
38
+ // let encrypted = cipher.update(text, 'utf8', 'hex');
39
+ // encrypted += cipher.final('hex');
40
+ // return iv.toString('hex') + ':' + encrypted;
41
+ // }
42
+
43
+ // // --- Decrypt ---
44
+ // export function decrypt(encryptedText: string): string {
45
+ // const [ivHex, encrypted] = encryptedText.split(':');
46
+ // if (!ivHex || !encrypted) throw new Error('Invalid encrypted text format.');
47
+
48
+ // const iv = Buffer.from(ivHex, 'hex');
49
+ // const decipher = crypto.createDecipheriv(algorithm, Buffer.from(secretKey, 'utf-8'), iv);
50
+ // let decrypted = decipher.update(encrypted, 'hex', 'utf8');
51
+ // decrypted += decipher.final('utf8');
52
+ // return decrypted;
53
+ // }
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /* PlayQ OpenAPI (OAS 3.1) -> schema TS files generator */
3
+
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+
7
+ type OpenApiDoc = {
8
+ openapi: string;
9
+ components?: {
10
+ schemas?: Record<string, any>;
11
+ };
12
+ };
13
+
14
+ type Args = {
15
+ input?: string;
16
+ url?: string;
17
+ outDir: string;
18
+ prefix: string;
19
+ indexFile: string;
20
+ };
21
+
22
+ function parseArgs(argv: string[]): Args {
23
+ const args: Args = {
24
+ outDir: "resources/schemas",
25
+ prefix: "", // optional export prefix e.g. "Api"
26
+ indexFile: "index.ts",
27
+ };
28
+
29
+ for (let i = 0; i < argv.length; i++) {
30
+ const a = argv[i];
31
+ const v = argv[i + 1];
32
+
33
+ if (a === "--input" && v) { args.input = v; i++; continue; }
34
+ if (a === "--url" && v) { args.url = v; i++; continue; }
35
+ if (a === "--outDir" && v) { args.outDir = v; i++; continue; }
36
+ if (a === "--prefix" && v) { args.prefix = v; i++; continue; }
37
+ if (a === "--indexFile" && v) { args.indexFile = v; i++; continue; }
38
+ }
39
+
40
+ if (!args.input && !args.url) {
41
+ throw new Error(`Provide either --input <path-to-openapi.json> OR --url <openapi-json-url>`);
42
+ }
43
+ if (args.input && args.url) {
44
+ throw new Error(`Use only one of --input or --url (not both).`);
45
+ }
46
+ return args;
47
+ }
48
+
49
+ async function loadOpenApi(args: Args): Promise<OpenApiDoc> {
50
+ if (args.input) {
51
+ const p = path.resolve(args.input);
52
+ const raw = fs.readFileSync(p, "utf8");
53
+ return JSON.parse(raw);
54
+ }
55
+
56
+ // URL
57
+ const res = await fetch(args.url!, {
58
+ headers: { Accept: "application/json" },
59
+ });
60
+ if (!res.ok) {
61
+ throw new Error(`Failed to fetch OpenAPI JSON from ${args.url}. HTTP ${res.status}`);
62
+ }
63
+ return (await res.json()) as OpenApiDoc;
64
+ }
65
+
66
+ function safeIdent(name: string): string {
67
+ // Make a valid TS identifier-ish name (keeps underscores)
68
+ const cleaned = name.replace(/[^a-zA-Z0-9_]/g, "_");
69
+ // Avoid leading digit
70
+ if (/^\d/.test(cleaned)) return "_" + cleaned;
71
+ return cleaned;
72
+ }
73
+
74
+ function exportNameFor(schemaName: string, prefix: string): string {
75
+ // AgentVersion -> AgentVersionSchema, optional prefix -> ApiAgentVersionSchema
76
+ const base = `${schemaName}Schema`;
77
+ return prefix ? `${prefix}${base}` : base;
78
+ }
79
+
80
+ function ensureOutDir(outDir: string) {
81
+ fs.mkdirSync(outDir, { recursive: true });
82
+ }
83
+
84
+ function writeSchemaFile(outDir: string, schemaKey: string, exportName: string, schema: any, openapiVersion: string) {
85
+ const fileName = `${schemaKey}.schema.ts`;
86
+ const fullPath = path.join(outDir, fileName);
87
+
88
+ const content =
89
+ `/* Auto-generated by PlayQ schema generator - OpenAPI ${openapiVersion} */\n` +
90
+ `/* Source schema: #/components/schemas/${schemaKey} */\n\n` +
91
+ `export const ${exportName} = ${JSON.stringify(schema, null, 2)} as const;\n`;
92
+
93
+ fs.writeFileSync(fullPath, content, "utf8");
94
+ }
95
+
96
+ function writeIndexFile(outDir: string, indexFile: string, entries: Array<{ schemaKey: string; exportName: string }>) {
97
+ const fullPath = path.join(outDir, indexFile);
98
+ const lines = entries
99
+ .sort((a, b) => a.schemaKey.localeCompare(b.schemaKey))
100
+ .map(e => `export { ${e.exportName} } from "./${e.schemaKey}.schema";`);
101
+
102
+ fs.writeFileSync(fullPath, lines.join("\n") + "\n", "utf8");
103
+ }
104
+
105
+ async function main() {
106
+ const args = parseArgs(process.argv.slice(2));
107
+ const doc = await loadOpenApi(args);
108
+
109
+ if (!doc.openapi?.startsWith("3.1")) {
110
+ // still might work, but you said OAS 3.1 and this generator assumes that
111
+ console.warn(`⚠️ OpenAPI version is "${doc.openapi}". Generator is optimised for 3.1.x.`);
112
+ }
113
+
114
+ const schemas = doc.components?.schemas ?? {};
115
+ const schemaKeys = Object.keys(schemas);
116
+ if (!schemaKeys.length) {
117
+ throw new Error(`No components.schemas found in the OpenAPI document.`);
118
+ }
119
+
120
+ const outDir = path.resolve(args.outDir);
121
+ ensureOutDir(outDir);
122
+
123
+ const indexEntries: Array<{ schemaKey: string; exportName: string }> = [];
124
+
125
+ for (const rawKey of schemaKeys) {
126
+ const schemaKey = safeIdent(rawKey);
127
+ const exportName = exportNameFor(schemaKey, args.prefix);
128
+ const schema = schemas[rawKey];
129
+
130
+ writeSchemaFile(outDir, schemaKey, exportName, schema, doc.openapi);
131
+ indexEntries.push({ schemaKey, exportName });
132
+ }
133
+
134
+ writeIndexFile(outDir, args.indexFile, indexEntries);
135
+
136
+ console.log(`✅ Generated ${schemaKeys.length} schemas into: ${outDir}`);
137
+ console.log(`✅ Barrel export: ${path.join(outDir, args.indexFile)}`);
138
+ }
139
+
140
+ main().catch(err => {
141
+ console.error(`❌ ${err instanceof Error ? err.message : String(err)}`);
142
+ process.exit(1);
143
+ });
@@ -0,0 +1,28 @@
1
+ // Utilities for Test Automation
2
+
3
+ function toCamelCase(input: string): string {
4
+ // Remove all non-alphanumeric characters except spaces
5
+ const cleaned = input.replace(/[^a-zA-Z0-9 ]/g, " ");
6
+
7
+ // Split by whitespace, lowercase first word, capitalize others
8
+ const words = cleaned.trim().split(/\s+/);
9
+
10
+ if (words.length === 0) return "";
11
+
12
+ let camelCase =
13
+ words[0].toLowerCase() +
14
+ words
15
+ .slice(1)
16
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
17
+ .join("");
18
+
19
+ // Ensure it doesn't start with a digit
20
+ if (/^[0-9]/.test(camelCase)) {
21
+ camelCase = "_" + camelCase;
22
+ }
23
+
24
+ return camelCase;
25
+ }
26
+
27
+
28
+ export { toCamelCase};
@@ -0,0 +1,28 @@
1
+ import { Page } from "@playwright/test";
2
+
3
+ export default class PlaywrightWrapper {
4
+
5
+ constructor(private page: Page) { }
6
+
7
+ async goto(url: string) {
8
+ await this.page.goto(url, {
9
+ waitUntil: "domcontentloaded"
10
+ });
11
+ }
12
+
13
+ async waitAndClick(locator: string) {
14
+ const element = this.page.locator(locator);
15
+ await element.waitFor({
16
+ state: "visible"
17
+ });
18
+ await element.click();
19
+ }
20
+
21
+ async navigateTo(link: string) {
22
+ await Promise.all([
23
+ this.page.waitForNavigation(),
24
+ this.page.click(link)
25
+ ])
26
+ }
27
+
28
+ }