@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,839 @@
1
+ // src/helper/util/sessionUtil.ts
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { Browser, BrowserContext, Page } from '@playwright/test';
5
+
6
+ type SessionKV = Record<string, string>;
7
+ type HybridState = {
8
+ storageState: any;
9
+ sessionByOrigin: Record<string, SessionKV>;
10
+ };
11
+
12
+ function originOf(url: string): string {
13
+ try {
14
+ const u = new URL(url);
15
+ return `${u.protocol}//${u.host}`;
16
+ } catch {
17
+ return '';
18
+ }
19
+ }
20
+
21
+ async function readSessionStorage(page: Page): Promise<SessionKV> {
22
+ try {
23
+ // Wait for page to be ready and check if context is still valid
24
+ await page.waitForLoadState('domcontentloaded', { timeout: 5000 });
25
+
26
+ return await page.evaluate(() => {
27
+ const out: Record<string, string> = {};
28
+ try {
29
+ const ss: any = (globalThis as any).sessionStorage;
30
+ if (ss) {
31
+ for (let i = 0; i < ss.length; i++) {
32
+ const k = ss.key(i);
33
+ if (k) {
34
+ const value = ss.getItem(k);
35
+ if (value) {
36
+ out[k] = value;
37
+ }
38
+ }
39
+ }
40
+ }
41
+ } catch (error) {
42
+ console.warn('Failed to read sessionStorage:', error);
43
+ }
44
+ return out;
45
+ });
46
+ } catch (error) {
47
+ console.warn(`Failed to read sessionStorage for ${page.url()}:`, error);
48
+ return {};
49
+ }
50
+ }
51
+
52
+ async function writeSessionStorage(page: Page, data: SessionKV): Promise<void> {
53
+ try {
54
+ await page.waitForLoadState('domcontentloaded', { timeout: 5000 });
55
+
56
+ await page.evaluate((d) => {
57
+ try {
58
+ const ss: any = (globalThis as any).sessionStorage;
59
+ if (ss) {
60
+ for (const [k, v] of Object.entries(d)) {
61
+ ss.setItem(k, v as string);
62
+ }
63
+ }
64
+ } catch (error) {
65
+ console.warn('Failed to write sessionStorage:', error);
66
+ }
67
+ }, data);
68
+ } catch (error) {
69
+ console.warn(`Failed to write sessionStorage for ${page.url()}:`, error);
70
+ }
71
+ }
72
+ // /**
73
+ // * Save session with auto-discovery of origins - no need to pass URLs manually
74
+ // */
75
+ // export async function saveSession(page: Page, sessionName: string, additionalUrls: string[] = []): Promise<void> {
76
+ // const sessionPath = `_Temp/sessions/${sessionName}.json`;
77
+
78
+ // try {
79
+ // console.log('🔄 Saving session state...');
80
+
81
+ // // First, save the storage state (cookies + localStorage)
82
+ // const storageState = await page.context().storageState();
83
+
84
+ // const state: HybridState = {
85
+ // storageState,
86
+ // sessionByOrigin: {},
87
+ // };
88
+
89
+ // // ✅ AUTO-DISCOVER ORIGINS from current browser context
90
+ // const discoveredOrigins = new Set<string>();
91
+
92
+ // // Get current page origin
93
+ // const currentUrl = page.url();
94
+ // if (currentUrl) {
95
+ // const currentOrigin = originOf(currentUrl);
96
+ // if (currentOrigin) {
97
+ // discoveredOrigins.add(currentOrigin);
98
+ // }
99
+ // }
100
+
101
+ // // Get origins from existing cookies (these are domains the user has visited)
102
+ // if (storageState.cookies) {
103
+ // storageState.cookies.forEach(cookie => {
104
+ // if (cookie.domain) {
105
+ // // Convert cookie domain to origin
106
+ // const protocol = cookie.secure ? 'https' : 'http';
107
+ // const domain = cookie.domain.startsWith('.') ? cookie.domain.substring(1) : cookie.domain;
108
+ // const origin = `${protocol}://${domain}`;
109
+ // discoveredOrigins.add(origin);
110
+ // }
111
+ // });
112
+ // }
113
+
114
+ // // Get origins from localStorage data
115
+ // if (storageState.origins) {
116
+ // storageState.origins.forEach(originData => {
117
+ // discoveredOrigins.add(originData.origin);
118
+ // });
119
+ // }
120
+
121
+ // // Add any additional URLs if provided
122
+ // additionalUrls.forEach(url => {
123
+ // const origin = originOf(url);
124
+ // if (origin) discoveredOrigins.add(origin);
125
+ // });
126
+
127
+ // // Filter out invalid origins and common unwanted ones
128
+ // const validOrigins = Array.from(discoveredOrigins).filter(origin => {
129
+ // try {
130
+ // const url = new URL(origin);
131
+ // // Skip localhost, file://, and other unwanted protocols
132
+ // return url.protocol === 'https:' || url.protocol === 'http:';
133
+ // } catch {
134
+ // return false;
135
+ // }
136
+ // });
137
+
138
+ // console.log(`🔍 Auto-discovered ${validOrigins.length} origins to save:`, validOrigins);
139
+
140
+ // // Save sessionStorage for current page first (most reliable)
141
+ // if (currentUrl) {
142
+ // const currentOrigin = originOf(currentUrl);
143
+ // if (currentOrigin && !page.isClosed()) {
144
+ // try {
145
+ // state.sessionByOrigin[currentOrigin] = await readSessionStorage(page);
146
+ // console.log(`✅ Saved sessionStorage for current origin: ${currentOrigin}`);
147
+ // } catch (error) {
148
+ // console.warn(`⚠️ Could not save sessionStorage for current origin ${currentOrigin}:`, error);
149
+ // }
150
+ // }
151
+ // }
152
+
153
+ // // Process other discovered origins
154
+ // const otherOrigins = validOrigins.filter(origin => origin !== originOf(currentUrl));
155
+
156
+ // for (const origin of otherOrigins) {
157
+ // try {
158
+ // console.log(`🔄 Navigating to origin for sessionStorage: ${origin}`);
159
+
160
+ // // Navigate with error handling
161
+ // await page.goto(origin, {
162
+ // waitUntil: 'domcontentloaded',
163
+ // timeout: 10000
164
+ // });
165
+
166
+ // // Small delay to ensure page is stable
167
+ // await page.waitForTimeout(500);
168
+
169
+ // state.sessionByOrigin[origin] = await readSessionStorage(page);
170
+ // console.log(`✅ Saved sessionStorage for origin: ${origin}`);
171
+
172
+ // } catch (error) {
173
+ // console.warn(`⚠️ Could not save sessionStorage for origin ${origin}:`, error);
174
+ // state.sessionByOrigin[origin] = {};
175
+ // }
176
+ // }
177
+
178
+ // // Ensure directory exists and save file
179
+ // await fs.promises.mkdir(path.dirname(sessionPath), { recursive: true });
180
+ // await fs.promises.writeFile(sessionPath, JSON.stringify(state, null, 2));
181
+
182
+ // console.log(`✅ Session saved to: ${sessionPath}`);
183
+ // console.log(`📊 Saved data for ${Object.keys(state.sessionByOrigin).length} origins`);
184
+
185
+ // } catch (error) {
186
+ // console.error('❌ Failed to save session:', error);
187
+ // throw error;
188
+ // }
189
+ // }
190
+ export async function saveSession(page: Page, sessionName: string, additionalUrls: string[] = []): Promise<void> {
191
+ const sessionPath = `_Temp/sessions/${sessionName}.json`;
192
+
193
+ try {
194
+ console.log('🔄 Saving session state...');
195
+
196
+ // First, save the storage state (cookies + localStorage)
197
+ const storageState = await page.context().storageState();
198
+
199
+ const state: HybridState = {
200
+ storageState,
201
+ sessionByOrigin: {},
202
+ };
203
+
204
+ // ✅ CONSERVATIVE AUTO-DISCOVERY
205
+ const discoveredOrigins = new Set<string>();
206
+
207
+ // Get current page origin (most reliable)
208
+ const currentUrl = page.url();
209
+ if (currentUrl) {
210
+ const currentOrigin = originOf(currentUrl);
211
+ if (currentOrigin) {
212
+ discoveredOrigins.add(currentOrigin);
213
+ }
214
+ }
215
+
216
+ // Get origins from localStorage data (proven origins)
217
+ if (storageState.origins) {
218
+ storageState.origins.forEach(originData => {
219
+ discoveredOrigins.add(originData.origin);
220
+ });
221
+ }
222
+
223
+ // Add manually provided URLs (if any)
224
+ additionalUrls.forEach(url => {
225
+ const origin = originOf(url);
226
+ if (origin) discoveredOrigins.add(origin);
227
+ });
228
+
229
+ // ✅ ONLY GET MICROSOFT ORIGINS FROM COOKIES (safer approach)
230
+ if (storageState.cookies) {
231
+ const microsoftCookies = storageState.cookies.filter(cookie =>
232
+ cookie.domain && (
233
+ cookie.domain.includes('microsoft') ||
234
+ cookie.domain.includes('office365') ||
235
+ cookie.domain.includes('dynamics')
236
+ )
237
+ );
238
+
239
+ microsoftCookies.forEach(cookie => {
240
+ if (cookie.domain) {
241
+ const protocol = cookie.secure ? 'https' : 'http';
242
+ let domain = cookie.domain.startsWith('.') ? cookie.domain.substring(1) : cookie.domain;
243
+
244
+ // ✅ Map to known Microsoft origins
245
+ const knownOrigins = [
246
+ 'https://login.microsoftonline.com',
247
+ 'https://graph.microsoft.com',
248
+ `https://${domain}`
249
+ ];
250
+
251
+ knownOrigins.forEach(origin => {
252
+ try {
253
+ new URL(origin); // Validate URL
254
+ discoveredOrigins.add(origin);
255
+ } catch {
256
+ // Skip invalid URLs
257
+ }
258
+ });
259
+ }
260
+ });
261
+ }
262
+
263
+ const validOrigins = Array.from(discoveredOrigins).filter(origin => {
264
+ try {
265
+ const url = new URL(origin);
266
+ return (url.protocol === 'https:' || url.protocol === 'http:') &&
267
+ !url.hostname.includes('localhost');
268
+ } catch {
269
+ return false;
270
+ }
271
+ });
272
+
273
+ console.log(`🔍 Auto-discovered ${validOrigins.length} origins to save:`, validOrigins);
274
+
275
+ // ✅ LIMIT TO MAXIMUM 5 ORIGINS (performance)
276
+ const limitedOrigins = validOrigins.slice(0, 5);
277
+ if (limitedOrigins.length < validOrigins.length) {
278
+ console.log(`⚠️ Limited to ${limitedOrigins.length} origins for performance`);
279
+ }
280
+
281
+ // Save sessionStorage for current page first
282
+ if (currentUrl) {
283
+ const currentOrigin = originOf(currentUrl);
284
+ if (currentOrigin && !page.isClosed()) {
285
+ try {
286
+ state.sessionByOrigin[currentOrigin] = await readSessionStorage(page);
287
+ console.log(`✅ Saved sessionStorage for current origin: ${currentOrigin}`);
288
+ } catch (error) {
289
+ console.warn(`⚠️ Could not save sessionStorage for current origin:`, error);
290
+ }
291
+ }
292
+ }
293
+
294
+ // Process other origins with conservative approach
295
+ const otherOrigins = limitedOrigins.filter(origin => origin !== originOf(currentUrl));
296
+
297
+ for (const origin of otherOrigins) {
298
+ try {
299
+ console.log(`🔄 Navigating to origin: ${origin}`);
300
+
301
+ // ✅ More robust navigation with shorter timeout
302
+ await page.goto(origin, {
303
+ waitUntil: 'domcontentloaded',
304
+ timeout: 8000
305
+ });
306
+
307
+ // ✅ Check if page is still valid
308
+ if (page.isClosed()) {
309
+ console.warn(`⚠️ Page closed during navigation to ${origin}`);
310
+ break;
311
+ }
312
+
313
+ await page.waitForTimeout(500);
314
+
315
+ const sessionData = await readSessionStorage(page);
316
+ state.sessionByOrigin[origin] = sessionData;
317
+ console.log(`✅ Saved sessionStorage for origin: ${origin}`);
318
+
319
+ } catch (error) {
320
+ console.warn(`⚠️ Could not save sessionStorage for origin ${origin}:`, error.message);
321
+ state.sessionByOrigin[origin] = {};
322
+
323
+ // ✅ If multiple failures, stop trying
324
+ const failureCount = Object.values(state.sessionByOrigin).filter(data =>
325
+ Object.keys(data).length === 0
326
+ ).length;
327
+
328
+ if (failureCount >= 3) {
329
+ console.warn('⚠️ Multiple origin failures, stopping further navigation');
330
+ break;
331
+ }
332
+ }
333
+ }
334
+
335
+ // Save file
336
+ await fs.promises.mkdir(path.dirname(sessionPath), { recursive: true });
337
+ await fs.promises.writeFile(sessionPath, JSON.stringify(state, null, 2));
338
+
339
+ console.log(`✅ Session saved to: ${sessionPath}`);
340
+ console.log(`📊 Saved data for ${Object.keys(state.sessionByOrigin).length} origins`);
341
+
342
+ } catch (error) {
343
+ console.error('❌ Failed to save session:', error);
344
+ throw error;
345
+ }
346
+ }
347
+
348
+ export async function saveSessionSimplified(page: Page, sessionName: string, additionalUrls: string[] = []): Promise<void> {
349
+ const sessionPath = `_Temp/sessions/${sessionName}.json`;
350
+
351
+ try {
352
+ console.log('🔄 Saving session state (simplified)...');
353
+
354
+ // Get storage state (cookies + localStorage for all origins)
355
+ const storageState = await page.context().storageState();
356
+
357
+ const state: HybridState = {
358
+ storageState,
359
+ sessionByOrigin: {},
360
+ };
361
+
362
+ // Only save sessionStorage for current page
363
+ const currentUrl = page.url();
364
+ if (currentUrl) {
365
+ const currentOrigin = originOf(currentUrl);
366
+ if (currentOrigin && !page.isClosed()) {
367
+ try {
368
+ state.sessionByOrigin[currentOrigin] = await readSessionStorage(page);
369
+ console.log(`✅ Saved sessionStorage for current origin: ${currentOrigin}`);
370
+ } catch (error) {
371
+ console.warn(`⚠️ Could not save sessionStorage for current origin:`, error);
372
+ }
373
+ }
374
+ }
375
+
376
+ // Optionally save sessionStorage for manually provided URLs only
377
+ if (additionalUrls.length > 0) {
378
+ for (const url of additionalUrls) {
379
+ try {
380
+ const origin = originOf(url);
381
+ if (origin && origin !== originOf(currentUrl)) {
382
+ console.log(`🔄 Saving sessionStorage for manually specified origin: ${origin}`);
383
+
384
+ await page.goto(origin, {
385
+ waitUntil: 'domcontentloaded',
386
+ timeout: 8000
387
+ });
388
+
389
+ await page.waitForTimeout(500);
390
+ state.sessionByOrigin[origin] = await readSessionStorage(page);
391
+ console.log(`✅ Saved sessionStorage for: ${origin}`);
392
+ }
393
+ } catch (error) {
394
+ console.warn(`⚠️ Could not save sessionStorage for ${url}:`, error);
395
+ }
396
+ }
397
+ }
398
+
399
+ // Save file
400
+ await fs.promises.mkdir(path.dirname(sessionPath), { recursive: true });
401
+ await fs.promises.writeFile(sessionPath, JSON.stringify(state, null, 2));
402
+
403
+ console.log(`✅ Session saved to: ${sessionPath}`);
404
+
405
+ } catch (error) {
406
+ console.error('❌ Failed to save session:', error);
407
+ throw error;
408
+ }
409
+ }
410
+ // /**
411
+ // * Save session with improved error handling and navigation safety
412
+ // */
413
+ // export async function saveSession(page: Page, sessionName: string, extraOrigins: string[] = []): Promise<void> {
414
+ // const filePath = `_Temp/sessions/${sessionName}.json`;
415
+ // try {
416
+ // console.log('🔄 Saving session state...');
417
+
418
+ // // First, save the storage state (cookies + localStorage)
419
+ // const storageState = await page.context().storageState();
420
+
421
+ // const state: HybridState = {
422
+ // storageState,
423
+ // sessionByOrigin: {},
424
+ // };
425
+
426
+ // const currentUrl = page.url();
427
+ // const currentOrigin = currentUrl ? originOf(currentUrl) : '';
428
+
429
+ // // Read sessionStorage from current page first (if valid)
430
+ // if (currentOrigin && !page.isClosed()) {
431
+ // try {
432
+ // state.sessionByOrigin[currentOrigin] = await readSessionStorage(page);
433
+ // console.log(`✅ Saved sessionStorage for current origin: ${currentOrigin}`);
434
+ // } catch (error) {
435
+ // console.warn(`⚠️ Could not save sessionStorage for current origin ${currentOrigin}:`, error);
436
+ // }
437
+ // }
438
+
439
+ // // Process extra origins
440
+ // const normalizedExtra = extraOrigins.map(originOf).filter(Boolean);
441
+ // const origins = Array.from(new Set(normalizedExtra.filter(o => o !== currentOrigin)));
442
+
443
+ // for (const origin of origins) {
444
+ // try {
445
+ // console.log(`🔄 Navigating to origin: ${origin}`);
446
+
447
+ // // Navigate with error handling
448
+ // await page.goto(origin, {
449
+ // waitUntil: 'domcontentloaded',
450
+ // timeout: 10000
451
+ // });
452
+
453
+ // // Small delay to ensure page is stable
454
+ // await page.waitForTimeout(500);
455
+
456
+ // state.sessionByOrigin[origin] = await readSessionStorage(page);
457
+ // console.log(`✅ Saved sessionStorage for origin: ${origin}`);
458
+
459
+ // } catch (error) {
460
+ // console.warn(`⚠️ Could not save sessionStorage for origin ${origin}:`, error);
461
+ // state.sessionByOrigin[origin] = {};
462
+ // }
463
+ // }
464
+
465
+ // // Ensure directory exists and save file
466
+ // await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
467
+ // await fs.promises.writeFile(filePath, JSON.stringify(state, null, 2));
468
+
469
+ // console.log(`✅ Session saved to: ${filePath}`);
470
+
471
+ // } catch (error) {
472
+ // console.error('❌ Failed to save session:', error);
473
+ // throw error;
474
+ // }
475
+ // }
476
+
477
+ /**
478
+ * Load session with improved error handling
479
+ */
480
+ export async function loadSession(
481
+ browser: Browser,
482
+ filePath: string,
483
+ warmOrigins: string[] = []
484
+ ): Promise<{ context: BrowserContext; page: Page }> {
485
+ try {
486
+ if (!fs.existsSync(filePath)) {
487
+ throw new Error(`Session file not found: ${filePath}`);
488
+ }
489
+
490
+ console.log(`🔄 Loading session from: ${filePath}`);
491
+
492
+ const saved: HybridState = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
493
+ const context = await browser.newContext({ storageState: saved.storageState });
494
+ const page = await context.newPage();
495
+
496
+ const warm = warmOrigins.map(originOf).filter(Boolean);
497
+ const origins = new Set([...Object.keys(saved.sessionByOrigin), ...warm]);
498
+
499
+ for (const origin of origins) {
500
+ try {
501
+ console.log(`🔄 Warming origin: ${origin}`);
502
+
503
+ await page.goto(origin, {
504
+ waitUntil: 'domcontentloaded',
505
+ timeout: 10000
506
+ });
507
+
508
+ // Small delay for stability
509
+ await page.waitForTimeout(500);
510
+
511
+ const data = saved.sessionByOrigin[origin];
512
+ if (data && Object.keys(data).length > 0) {
513
+ await writeSessionStorage(page, data);
514
+ console.log(`✅ Restored sessionStorage for origin: ${origin}`);
515
+ }
516
+
517
+ } catch (error) {
518
+ console.warn(`⚠️ Could not restore sessionStorage for origin ${origin}:`, error);
519
+ }
520
+ }
521
+
522
+ console.log('✅ Session loaded successfully');
523
+ return { context, page };
524
+
525
+ } catch (error) {
526
+ console.error('❌ Failed to load session:', error);
527
+ throw error;
528
+ }
529
+ }
530
+ /**
531
+ * Load session into existing context without creating new browser/page
532
+ * Auto-discovers origins from saved session data - no need to pass URLs
533
+ */
534
+ export async function loadSessionIntoExistingContext(page: Page, sessionName: string, additionalUrls: string[] = []): Promise<void> {
535
+ const sessionPath = `_Temp/sessions/${sessionName}.json`;
536
+ try {
537
+ if (!fs.existsSync(sessionPath)) {
538
+ throw new Error(`Session file not found: ${sessionPath}`);
539
+ }
540
+
541
+ console.log(`🔄 Loading session into existing context from: ${sessionPath}`);
542
+
543
+ const savedData = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
544
+
545
+ // Handle both hybrid state and simple storage state formats
546
+ let storageState: any;
547
+ let sessionByOrigin: Record<string, SessionKV> = {};
548
+
549
+ if (savedData.storageState && savedData.sessionByOrigin) {
550
+ // Hybrid state format
551
+ storageState = savedData.storageState;
552
+ sessionByOrigin = savedData.sessionByOrigin;
553
+ } else if (savedData.cookies || savedData.origins) {
554
+ // Simple storage state format
555
+ storageState = savedData;
556
+ } else {
557
+ throw new Error('Invalid session file format');
558
+ }
559
+
560
+ // Add cookies to existing context
561
+ if (storageState?.cookies && Array.isArray(storageState.cookies)) {
562
+ try {
563
+ await page.context().addCookies(storageState.cookies);
564
+ console.log('✅ Cookies restored to existing context');
565
+ } catch (error) {
566
+ console.warn('⚠️ Failed to add cookies:', error);
567
+ }
568
+ }
569
+
570
+ // ✅ AUTO-DISCOVER ORIGINS - No need to pass URLs manually
571
+ const discoveredOrigins = new Set<string>();
572
+
573
+ // Get origins from sessionStorage data
574
+ Object.keys(sessionByOrigin || {}).forEach(origin => {
575
+ discoveredOrigins.add(origin);
576
+ });
577
+
578
+ // Get origins from localStorage data
579
+ if (storageState?.origins) {
580
+ storageState.origins.forEach((o: any) => {
581
+ discoveredOrigins.add(o.origin);
582
+ });
583
+ }
584
+
585
+ // Add any additional URLs if provided (optional)
586
+ additionalUrls.forEach(url => {
587
+ const origin = originOf(url);
588
+ if (origin) discoveredOrigins.add(origin);
589
+ });
590
+
591
+ console.log(`🔍 Discovered ${discoveredOrigins.size} origins to restore:`, Array.from(discoveredOrigins));
592
+
593
+ // Restore storage for each discovered origin
594
+ for (const origin of discoveredOrigins) {
595
+ try {
596
+ console.log(`🔄 Restoring storage for origin: ${origin}`);
597
+
598
+ // Navigate to the origin
599
+ await page.goto(origin, {
600
+ waitUntil: 'domcontentloaded',
601
+ timeout: 15000
602
+ });
603
+
604
+ // Wait for page stability
605
+ await page.waitForTimeout(1000);
606
+
607
+ // Restore localStorage
608
+ const originData = storageState?.origins?.find((o: any) => o.origin === origin);
609
+ if (originData?.localStorage && Array.isArray(originData.localStorage)) {
610
+ for (const item of originData.localStorage) {
611
+ try {
612
+ await page.evaluate(
613
+ ({ name, value }) => {
614
+ const ls: any = (globalThis as any).localStorage;
615
+ if (ls) ls.setItem(name, value);
616
+ },
617
+ item
618
+ );
619
+ } catch (error) {
620
+ console.warn(`Failed to set localStorage item ${item.name}:`, error);
621
+ }
622
+ }
623
+ console.log(`✅ localStorage restored for ${origin}`);
624
+ }
625
+
626
+ // Restore sessionStorage
627
+ const sessionData = sessionByOrigin[origin];
628
+ if (sessionData && Object.keys(sessionData).length > 0) {
629
+ try {
630
+ await page.evaluate((data) => {
631
+ const ss: any = (globalThis as any).sessionStorage;
632
+ if (ss) {
633
+ for (const [key, value] of Object.entries(data)) {
634
+ ss.setItem(key, value as string);
635
+ }
636
+ }
637
+ }, sessionData);
638
+ console.log(`✅ sessionStorage restored for ${origin}`);
639
+ } catch (error) {
640
+ console.warn(`Failed to restore sessionStorage for ${origin}:`, error);
641
+ }
642
+ }
643
+
644
+ } catch (error) {
645
+ console.warn(`⚠️ Could not restore storage for origin ${origin}:`, error);
646
+ }
647
+ }
648
+
649
+ console.log('✅ Session loaded into existing context successfully');
650
+
651
+ } catch (error) {
652
+ console.error('❌ Failed to load session into existing context:', error);
653
+ throw error;
654
+ }
655
+ }
656
+ // /**
657
+ // * Check if session file exists and is valid
658
+ // */
659
+ // export function isSessionValid(filePath: string, maxAgeHours: number = 24): boolean {
660
+ // try {
661
+ // if (!fs.existsSync(filePath)) {
662
+ // return false;
663
+ // }
664
+
665
+ // const stats = fs.statSync(filePath);
666
+ // const ageInHours = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60);
667
+
668
+ // return ageInHours < maxAgeHours;
669
+ // } catch {
670
+ // return false;
671
+ // }
672
+ // }
673
+
674
+ /**
675
+ * Delete session file
676
+ */
677
+ export function deleteSession(filePath: string): void {
678
+ try {
679
+ if (fs.existsSync(filePath)) {
680
+ fs.unlinkSync(filePath);
681
+ console.log(`🗑️ Session file deleted: ${filePath}`);
682
+ }
683
+ } catch (error) {
684
+ console.warn(`⚠️ Could not delete session file: ${error}`);
685
+ }
686
+ }
687
+
688
+ // /**
689
+ // * Load session into existing context without creating new browser/page
690
+ // */
691
+ // export async function loadSessionIntoExistingContext(page: Page, sessionName: string, urls: string[] = []): Promise<void> {
692
+ // const sessionPath = `_Temp/sessions/${sessionName}.json`;
693
+ // try {
694
+ // if (!fs.existsSync(sessionPath)) {
695
+ // throw new Error(`Session file not found: ${sessionPath}`);
696
+ // }
697
+
698
+ // console.log(`🔄 Loading session into existing context from: ${sessionPath}`);
699
+
700
+ // const savedData = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
701
+
702
+ // // Handle both hybrid state and simple storage state formats
703
+ // let storageState: any;
704
+ // let sessionByOrigin: Record<string, SessionKV> = {};
705
+
706
+ // if (savedData.storageState && savedData.sessionByOrigin) {
707
+ // // Hybrid state format
708
+ // storageState = savedData.storageState;
709
+ // sessionByOrigin = savedData.sessionByOrigin;
710
+ // } else if (savedData.cookies || savedData.origins) {
711
+ // // Simple storage state format
712
+ // storageState = savedData;
713
+ // } else {
714
+ // throw new Error('Invalid session file format');
715
+ // }
716
+
717
+ // // Add cookies to existing context
718
+ // if (storageState?.cookies && Array.isArray(storageState.cookies)) {
719
+ // try {
720
+ // await page.context().addCookies(storageState.cookies);
721
+ // console.log('✅ Cookies restored to existing context');
722
+ // } catch (error) {
723
+ // console.warn('⚠️ Failed to add cookies:', error);
724
+ // }
725
+ // }
726
+
727
+ // // Determine origins to restore
728
+ // const targetUrls = urls.length > 0 ? urls : ['https://login.microsoftonline.com'];
729
+ // const origins = new Set<string>();
730
+
731
+ // // Add origins from URLs
732
+ // targetUrls.forEach(url => {
733
+ // const origin = originOf(url);
734
+ // if (origin) origins.add(origin);
735
+ // });
736
+
737
+ // // Add origins from session data
738
+ // if (storageState?.origins) {
739
+ // storageState.origins.forEach((o: any) => origins.add(o.origin));
740
+ // }
741
+
742
+ // Object.keys(sessionByOrigin).forEach(origin => origins.add(origin));
743
+
744
+ // // Restore storage for each origin
745
+ // for (const origin of origins) {
746
+ // try {
747
+ // console.log(`🔄 Restoring storage for origin: ${origin}`);
748
+
749
+ // // Navigate to the origin
750
+ // await page.goto(origin, {
751
+ // waitUntil: 'domcontentloaded',
752
+ // timeout: 15000
753
+ // });
754
+
755
+ // // Wait for page stability
756
+ // await page.waitForTimeout(1000);
757
+
758
+ // // Restore localStorage
759
+ // const originData = storageState?.origins?.find((o: any) => o.origin === origin);
760
+ // if (originData?.localStorage && Array.isArray(originData.localStorage)) {
761
+ // for (const item of originData.localStorage) {
762
+ // try {
763
+ // await page.evaluate(
764
+ // ({ name, value }) => {
765
+ // localStorage.setItem(name, value);
766
+ // },
767
+ // item
768
+ // );
769
+ // } catch (error) {
770
+ // console.warn(`Failed to set localStorage item ${item.name}:`, error);
771
+ // }
772
+ // }
773
+ // console.log(`✅ localStorage restored for ${origin}`);
774
+ // }
775
+
776
+ // // Restore sessionStorage
777
+ // const sessionData = sessionByOrigin[origin];
778
+ // if (sessionData && Object.keys(sessionData).length > 0) {
779
+ // try {
780
+ // await page.evaluate((data) => {
781
+ // for (const [key, value] of Object.entries(data)) {
782
+ // sessionStorage.setItem(key, value as string);
783
+ // }
784
+ // }, sessionData);
785
+ // console.log(`✅ sessionStorage restored for ${origin}`);
786
+ // } catch (error) {
787
+ // console.warn(`Failed to restore sessionStorage for ${origin}:`, error);
788
+ // }
789
+ // }
790
+
791
+ // } catch (error) {
792
+ // console.warn(`⚠️ Could not restore storage for origin ${origin}:`, error);
793
+ // }
794
+ // }
795
+
796
+ // // Navigate to the target URL if provided
797
+ // if (urls.length > 0) {
798
+ // const targetUrl = urls[0];
799
+ // console.log(`🔄 Navigating to target URL: ${targetUrl}`);
800
+
801
+ // try {
802
+ // await page.goto(targetUrl, {
803
+ // waitUntil: 'domcontentloaded',
804
+ // timeout: 15000
805
+ // });
806
+ // await page.waitForTimeout(500);
807
+ // } catch (error) {
808
+ // console.warn(`⚠️ Failed to navigate to ${targetUrl}:`, error);
809
+ // }
810
+ // }
811
+
812
+ // console.log('✅ Session loaded into existing context successfully');
813
+
814
+ // } catch (error) {
815
+ // console.error('❌ Failed to load session into existing context:', error);
816
+ // throw error;
817
+ // }
818
+ // }
819
+
820
+ /**
821
+ * Checks if the session file exists and is not older than maxAgeHours.
822
+ * @param filePath - Path to the session file.
823
+ * @param maxAgeHours - Maximum allowed age in hours.
824
+ * @returns true if session file exists and is valid, false otherwise.
825
+ */
826
+ export function isSessionValid(sessionName: string, maxAgeHours: number = 24): boolean {
827
+ const sessionPath = `_Temp/sessions/${sessionName}.json`;
828
+ try {
829
+ if (!fs.existsSync(sessionPath)) {
830
+ return false;
831
+ }
832
+ const stats = fs.statSync(sessionPath);
833
+ const ageMs = Date.now() - stats.mtime.getTime();
834
+ const ageHours = ageMs / (1000 * 60 * 60);
835
+ return ageHours < maxAgeHours;
836
+ } catch {
837
+ return false;
838
+ }
839
+ }