@probolabs/playwright 1.0.8 → 1.0.10
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.
- package/dist/index.d.ts +67 -2
- package/dist/index.js +302 -106
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
- package/dist/types/actions.d.ts.map +0 -1
- package/dist/types/fixtures.d.ts.map +0 -1
- package/dist/types/highlight.d.ts.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/nav-tracker.d.ts.map +0 -1
- package/dist/types/otp.d.ts.map +0 -1
- package/dist/types/probo-logger.d.ts.map +0 -1
- package/dist/types/replay-utils.d.ts.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -240,6 +240,71 @@ declare class NavTracker {
|
|
|
240
240
|
static resetInstance(): void;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
interface MailinatorMessage {
|
|
244
|
+
id: string;
|
|
245
|
+
subject: string;
|
|
246
|
+
from: string;
|
|
247
|
+
origfrom?: string;
|
|
248
|
+
to: string;
|
|
249
|
+
time: number;
|
|
250
|
+
seconds_ago: number;
|
|
251
|
+
domain?: string;
|
|
252
|
+
source?: string;
|
|
253
|
+
parts?: Array<{
|
|
254
|
+
headers: Record<string, string>;
|
|
255
|
+
body: string;
|
|
256
|
+
}>;
|
|
257
|
+
textBody?: string;
|
|
258
|
+
htmlBody?: string;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* OTP utility class for working with Mailinator API
|
|
262
|
+
*/
|
|
263
|
+
declare class OTP {
|
|
264
|
+
/**
|
|
265
|
+
* Fetches the last messages from Mailinator for a specific inbox
|
|
266
|
+
* @param inbox - The inbox name to check (without @domain)
|
|
267
|
+
* @param since - Unix timestamp to fetch messages since (optional)
|
|
268
|
+
* @returns Promise<MailinatorMessage[]> - Array of messages
|
|
269
|
+
*/
|
|
270
|
+
static fetchLastMessages(inbox: string, since?: number): Promise<MailinatorMessage[]>;
|
|
271
|
+
/**
|
|
272
|
+
* Fetches a specific message by ID
|
|
273
|
+
* @param messageId - The message ID
|
|
274
|
+
* @returns Promise<MailinatorMessage> - The message details
|
|
275
|
+
*/
|
|
276
|
+
static fetchMessage(messageId: string): Promise<MailinatorMessage>;
|
|
277
|
+
/**
|
|
278
|
+
* Extracts OTP codes from message content
|
|
279
|
+
* @param message - The message to extract OTP from
|
|
280
|
+
* @returns string | null - The OTP code or null if not found
|
|
281
|
+
*/
|
|
282
|
+
static extractOTPFromMessage(message: MailinatorMessage): string | null;
|
|
283
|
+
/**
|
|
284
|
+
* Gets the text content from a message for debugging/display purposes
|
|
285
|
+
* @param message - The message to extract text from
|
|
286
|
+
* @returns string - The text content of the message
|
|
287
|
+
*/
|
|
288
|
+
static getMessageTextContent(message: MailinatorMessage): string;
|
|
289
|
+
/**
|
|
290
|
+
* Fetches messages from all inboxes in the domain
|
|
291
|
+
* @param limit - Number of messages to fetch (default: 50)
|
|
292
|
+
* @param sort - Sort order: 'ascending' or 'descending' (default: 'descending')
|
|
293
|
+
* @param full - Whether to fetch full message content (default: false)
|
|
294
|
+
* @returns Promise<MailinatorMessage[]> - Array of messages from all inboxes
|
|
295
|
+
*/
|
|
296
|
+
static fetchAllInboxMessages(limit?: number, sort?: 'ascending' | 'descending', full?: boolean): Promise<MailinatorMessage[]>;
|
|
297
|
+
/**
|
|
298
|
+
* Waits for an OTP to arrive in the inbox and extracts it
|
|
299
|
+
* @param inbox - The inbox name to monitor (optional - if not provided, searches all inboxes)
|
|
300
|
+
* @param timeout - Maximum time to wait in milliseconds (default: 30000)
|
|
301
|
+
* @param checkInterval - How often to check in milliseconds (default: 1000)
|
|
302
|
+
* @param checkRecentMessagesSinceMs - When > 0, check messages from the last X milliseconds and return the most recent OTP (default: 0)
|
|
303
|
+
* @returns Promise<string | null> - The extracted OTP code or null if timeout/no OTP found
|
|
304
|
+
*/
|
|
305
|
+
static waitForOTP(inbox?: string, timeout?: number, checkInterval?: number, checkRecentMessagesSinceMs?: number): Promise<string | null>;
|
|
306
|
+
}
|
|
307
|
+
|
|
243
308
|
/**
|
|
244
309
|
* Configuration options for Probo client
|
|
245
310
|
*/
|
|
@@ -284,5 +349,5 @@ declare class Probo {
|
|
|
284
349
|
askAIHelper(page: Page, question: string): Promise<ServerResponse>;
|
|
285
350
|
}
|
|
286
351
|
|
|
287
|
-
export { Highlighter, NavTracker, Probo, ProboPlaywright, findClosestVisibleElement };
|
|
288
|
-
export type { ElementTagType, RunStepOptions };
|
|
352
|
+
export { Highlighter, NavTracker, OTP, Probo, ProboPlaywright, findClosestVisibleElement };
|
|
353
|
+
export type { ElementTagType, MailinatorMessage, RunStepOptions };
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,7 @@ var PlaywrightAction;
|
|
|
44
44
|
PlaywrightAction["EXECUTE_SCRIPT"] = "EXECUTE_SCRIPT";
|
|
45
45
|
PlaywrightAction["UPLOAD_FILES"] = "UPLOAD_FILES";
|
|
46
46
|
PlaywrightAction["WAIT_FOR"] = "WAIT_FOR";
|
|
47
|
+
PlaywrightAction["WAIT_FOR_OTP"] = "WAIT_FOR_OTP";
|
|
47
48
|
})(PlaywrightAction || (PlaywrightAction = {}));
|
|
48
49
|
|
|
49
50
|
// WebSocketsMessageType enum for WebSocket and event message types shared across the app
|
|
@@ -64,6 +65,7 @@ var WebSocketsMessageType;
|
|
|
64
65
|
WebSocketsMessageType["INTERACTION_STEP_CREATED"] = "INTERACTION_STEP_CREATED";
|
|
65
66
|
WebSocketsMessageType["INTERACTION_APPLY_AI_SUMMARY_COMPLETED"] = "INTERACTION_APPLY_AI_SUMMARY_COMPLETED";
|
|
66
67
|
WebSocketsMessageType["INTERACTION_APPLY_AI_SUMMARY_ERROR"] = "INTERACTION_APPLY_AI_SUMMARY_ERROR";
|
|
68
|
+
WebSocketsMessageType["OTP_RETRIEVED"] = "OTP_RETRIEVED";
|
|
67
69
|
})(WebSocketsMessageType || (WebSocketsMessageType = {}));
|
|
68
70
|
|
|
69
71
|
/**
|
|
@@ -616,7 +618,7 @@ const decorateErrorWithCounts = (error, attemptNumber, options) => {
|
|
|
616
618
|
return error;
|
|
617
619
|
};
|
|
618
620
|
|
|
619
|
-
async function pRetry
|
|
621
|
+
async function pRetry(input, options) {
|
|
620
622
|
return new Promise((resolve, reject) => {
|
|
621
623
|
options = {...options};
|
|
622
624
|
options.onFailedAttempt ??= () => {};
|
|
@@ -743,7 +745,7 @@ class ApiClient {
|
|
|
743
745
|
}
|
|
744
746
|
async createStep(options) {
|
|
745
747
|
apiLogger.debug('creating step ', options.stepPrompt);
|
|
746
|
-
return pRetry
|
|
748
|
+
return pRetry(async () => {
|
|
747
749
|
const response = await fetch(`${this.apiUrl}/step-runners/`, {
|
|
748
750
|
method: 'POST',
|
|
749
751
|
headers: this.getHeaders(),
|
|
@@ -770,7 +772,7 @@ class ApiClient {
|
|
|
770
772
|
async patchStep(stepId, fields) {
|
|
771
773
|
// Use PATCH /steps/:id/ endpoint
|
|
772
774
|
apiLogger.debug(`patching step #${stepId}`);
|
|
773
|
-
return pRetry
|
|
775
|
+
return pRetry(async () => {
|
|
774
776
|
const response = await fetch(`${this.apiUrl}/steps/${stepId}/`, {
|
|
775
777
|
method: 'PATCH',
|
|
776
778
|
headers: this.getHeaders(),
|
|
@@ -788,7 +790,7 @@ class ApiClient {
|
|
|
788
790
|
}
|
|
789
791
|
async resolveNextInstruction(stepId, instruction, aiModel) {
|
|
790
792
|
apiLogger.debug(`resolving next instruction: ${instruction}`);
|
|
791
|
-
return pRetry
|
|
793
|
+
return pRetry(async () => {
|
|
792
794
|
apiLogger.debug(`API client: Resolving next instruction for step ${stepId}`);
|
|
793
795
|
const cleanInstruction = cleanupInstructionElements(instruction);
|
|
794
796
|
const response = await fetch(`${this.apiUrl}/step-runners/${stepId}/run/`, {
|
|
@@ -810,7 +812,7 @@ class ApiClient {
|
|
|
810
812
|
});
|
|
811
813
|
}
|
|
812
814
|
async uploadScreenshot(screenshot_bytes) {
|
|
813
|
-
return pRetry
|
|
815
|
+
return pRetry(async () => {
|
|
814
816
|
const response = await fetch(`${this.apiUrl}/upload-screenshots/`, {
|
|
815
817
|
method: 'POST',
|
|
816
818
|
headers: {
|
|
@@ -830,7 +832,7 @@ class ApiClient {
|
|
|
830
832
|
}
|
|
831
833
|
async findStepByPrompt(prompt, scenarioName, url = '') {
|
|
832
834
|
apiLogger.debug(`Finding step by prompt: ${prompt} and scenario: ${scenarioName}`);
|
|
833
|
-
return pRetry
|
|
835
|
+
return pRetry(async () => {
|
|
834
836
|
const response = await fetch(`${this.apiUrl}/step-runners/find-step-by-prompt/`, {
|
|
835
837
|
method: 'POST',
|
|
836
838
|
headers: this.getHeaders(),
|
|
@@ -864,7 +866,7 @@ class ApiClient {
|
|
|
864
866
|
});
|
|
865
867
|
}
|
|
866
868
|
async resetStep(stepId) {
|
|
867
|
-
return pRetry
|
|
869
|
+
return pRetry(async () => {
|
|
868
870
|
const response = await fetch(`${this.apiUrl}/steps/${stepId}/reset/`, {
|
|
869
871
|
method: 'POST',
|
|
870
872
|
headers: this.getHeaders(),
|
|
@@ -876,7 +878,7 @@ class ApiClient {
|
|
|
876
878
|
async interactionToStep(scenarioName, interaction, position = -1) {
|
|
877
879
|
// Use POST /interaction-to-step/ endpoint
|
|
878
880
|
apiLogger.debug(`converting interaction #${interaction.interactionId} to step`);
|
|
879
|
-
return pRetry
|
|
881
|
+
return pRetry(async () => {
|
|
880
882
|
var _a, _b;
|
|
881
883
|
const response = await fetch(`${this.apiUrl}/interaction-to-step/`, {
|
|
882
884
|
method: 'POST',
|
|
@@ -910,7 +912,7 @@ class ApiClient {
|
|
|
910
912
|
async actionToPrompt(action2promptInput, aiModel) {
|
|
911
913
|
// Use POST /action-to-prompt/ endpoint
|
|
912
914
|
apiLogger.debug(`running action2prompt for step #${action2promptInput.step_id}`);
|
|
913
|
-
return pRetry
|
|
915
|
+
return pRetry(async () => {
|
|
914
916
|
const response = await fetch(`${this.apiUrl}/steps/${action2promptInput.step_id}/action_to_prompt/`, {
|
|
915
917
|
method: 'POST',
|
|
916
918
|
headers: this.getHeaders(),
|
|
@@ -941,7 +943,7 @@ class ApiClient {
|
|
|
941
943
|
async askAI(question, scenarioName, screenshot, aiModel) {
|
|
942
944
|
apiLogger.debug(`Asking AI question: "${question}", scenarioName: ${scenarioName}`);
|
|
943
945
|
apiLogger.debug(`headers: ${JSON.stringify(this.getHeaders())}`);
|
|
944
|
-
return pRetry
|
|
946
|
+
return pRetry(async () => {
|
|
945
947
|
const response = await fetch(`${this.apiUrl}/api/ask-ai/`, {
|
|
946
948
|
method: 'POST',
|
|
947
949
|
headers: this.getHeaders(),
|
|
@@ -1277,103 +1279,286 @@ class Highlighter {
|
|
|
1277
1279
|
}
|
|
1278
1280
|
}
|
|
1279
1281
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1282
|
+
const MAILINATOR_API_KEY = '5bfef31518e84dfbb861b36f59259695';
|
|
1283
|
+
const MAILINATOR_DOMAIN = 'probolabs.testinator.com';
|
|
1284
|
+
/**
|
|
1285
|
+
* OTP utility class for working with Mailinator API
|
|
1286
|
+
*/
|
|
1287
|
+
class OTP {
|
|
1288
|
+
/**
|
|
1289
|
+
* Fetches the last messages from Mailinator for a specific inbox
|
|
1290
|
+
* @param inbox - The inbox name to check (without @domain)
|
|
1291
|
+
* @param since - Unix timestamp to fetch messages since (optional)
|
|
1292
|
+
* @returns Promise<MailinatorMessage[]> - Array of messages
|
|
1293
|
+
*/
|
|
1294
|
+
static async fetchLastMessages(inbox, since) {
|
|
1295
|
+
try {
|
|
1296
|
+
// Use the correct Mailinator API endpoint structure
|
|
1297
|
+
const url = `https://api.mailinator.com/v2/domains/${MAILINATOR_DOMAIN}/inboxes/${inbox}`;
|
|
1298
|
+
const response = await fetch(url, {
|
|
1299
|
+
method: 'GET',
|
|
1300
|
+
headers: {
|
|
1301
|
+
'Authorization': `Bearer ${MAILINATOR_API_KEY}`,
|
|
1302
|
+
'Content-Type': 'application/json'
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
if (!response.ok) {
|
|
1306
|
+
throw new Error(`Mailinator API error: ${response.status} ${response.statusText}`);
|
|
1307
|
+
}
|
|
1308
|
+
const data = await response.json();
|
|
1309
|
+
// The API returns messages in 'msgs' property, not 'messages'
|
|
1310
|
+
const messages = data.msgs || data.messages || data || [];
|
|
1311
|
+
// Filter messages by 'since' timestamp if provided
|
|
1312
|
+
if (since) {
|
|
1313
|
+
return messages.filter((message) => message.time >= since);
|
|
1314
|
+
}
|
|
1315
|
+
return messages;
|
|
1316
|
+
}
|
|
1317
|
+
catch (error) {
|
|
1318
|
+
console.error('Error fetching messages from Mailinator:', error);
|
|
1319
|
+
throw error;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Fetches a specific message by ID
|
|
1324
|
+
* @param messageId - The message ID
|
|
1325
|
+
* @returns Promise<MailinatorMessage> - The message details
|
|
1326
|
+
*/
|
|
1327
|
+
static async fetchMessage(messageId) {
|
|
1328
|
+
try {
|
|
1329
|
+
const url = `https://api.mailinator.com/v2/domains/${MAILINATOR_DOMAIN}/messages/${messageId}`;
|
|
1330
|
+
const response = await fetch(url, {
|
|
1331
|
+
method: 'GET',
|
|
1332
|
+
headers: {
|
|
1333
|
+
'Authorization': `Bearer ${MAILINATOR_API_KEY}`,
|
|
1334
|
+
'Content-Type': 'application/json'
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
if (!response.ok) {
|
|
1338
|
+
throw new Error(`Mailinator API error: ${response.status} ${response.statusText}`);
|
|
1339
|
+
}
|
|
1340
|
+
return await response.json();
|
|
1341
|
+
}
|
|
1342
|
+
catch (error) {
|
|
1343
|
+
console.error('Error fetching message from Mailinator:', error);
|
|
1344
|
+
throw error;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Extracts OTP codes from message content
|
|
1349
|
+
* @param message - The message to extract OTP from
|
|
1350
|
+
* @returns string | null - The OTP code or null if not found
|
|
1351
|
+
*/
|
|
1352
|
+
static extractOTPFromMessage(message) {
|
|
1353
|
+
// Common OTP patterns - ordered by specificity (most specific first)
|
|
1354
|
+
const otpPatterns = [
|
|
1355
|
+
// Very specific patterns for common OTP formats
|
|
1356
|
+
/(?:verification\s+code|verification\s+number|6-digit\s+verification\s+code)[\s\S]*?(\d{6})/i,
|
|
1357
|
+
/(?:use\s+the\s+6-digit\s+verification\s+code\s+below)[\s\S]*?(\d{6})/i,
|
|
1358
|
+
/(?:code\s+below\s+to\s+complete)[\s\S]*?(\d{6})/i,
|
|
1359
|
+
/(?:verification\s+code)[\s\S]*?(\d{6})/i,
|
|
1360
|
+
// Patterns with context around the code
|
|
1361
|
+
/(?:OTP|code|verification code|verification):\s*(\d{4,8})/i,
|
|
1362
|
+
/(?:enter|use|input)\s+(?:the\s+)?(?:code|OTP|verification code):\s*(\d{4,8})/i,
|
|
1363
|
+
/(?:your\s+)?(?:verification\s+)?(?:code\s+is|OTP\s+is):\s*(\d{4,8})/i,
|
|
1364
|
+
/(?:code|OTP|pin):\s*(\d{4,8})/i,
|
|
1365
|
+
/(?:code|OTP)\s+is\s+(\d{4,8})/i,
|
|
1366
|
+
// Look for codes in specific contexts (avoiding URLs and tracking IDs)
|
|
1367
|
+
/(?:complete\s+your\s+sign-in)[\s\S]*?(\d{6})/i,
|
|
1368
|
+
/(?:valid\s+for\s+the\s+next)[\s\S]*?(\d{6})/i,
|
|
1369
|
+
// Fallback: 6-digit numbers (most common OTP length)
|
|
1370
|
+
/(?:^|\s)(\d{6})(?:\s|$)/i,
|
|
1371
|
+
// Last resort: 4-8 digit numbers (but avoid very long ones that are likely tracking IDs)
|
|
1372
|
+
/(?:^|\s)(\d{4,8})(?:\s|$)/i
|
|
1373
|
+
];
|
|
1374
|
+
// Helper function to check if a number is likely an OTP (not a tracking ID, phone number, etc.)
|
|
1375
|
+
const isValidOTP = (number) => {
|
|
1376
|
+
// Common OTP lengths
|
|
1377
|
+
if (number.length < 4 || number.length > 8)
|
|
1378
|
+
return false;
|
|
1379
|
+
// Only filter out very obvious non-OTPs
|
|
1380
|
+
// Avoid numbers that are all the same digit (unlikely to be OTPs)
|
|
1381
|
+
if (/^(\d)\1+$/.test(number))
|
|
1382
|
+
return false;
|
|
1383
|
+
// Avoid very long numbers that are clearly tracking IDs (but be more lenient)
|
|
1384
|
+
if (number.length >= 8 && parseInt(number) > 99999999)
|
|
1385
|
+
return false;
|
|
1386
|
+
return true;
|
|
1387
|
+
};
|
|
1388
|
+
// Helper function to extract OTP from text content
|
|
1389
|
+
const extractFromText = (text) => {
|
|
1390
|
+
if (!text)
|
|
1391
|
+
return null;
|
|
1392
|
+
for (const pattern of otpPatterns) {
|
|
1393
|
+
const match = text.match(pattern);
|
|
1394
|
+
if (match && match[1] && isValidOTP(match[1])) {
|
|
1395
|
+
return match[1];
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return null;
|
|
1399
|
+
};
|
|
1400
|
+
// Helper function to strip HTML tags
|
|
1401
|
+
const stripHtml = (html) => {
|
|
1402
|
+
return html.replace(/<[^>]*>/g, '').replace(/&[^;]+;/g, ' ');
|
|
1403
|
+
};
|
|
1404
|
+
// 1. Try to extract from text body first (preferred)
|
|
1405
|
+
if (message.textBody) {
|
|
1406
|
+
const otp = extractFromText(message.textBody);
|
|
1407
|
+
if (otp)
|
|
1408
|
+
return otp;
|
|
1409
|
+
}
|
|
1410
|
+
// 2. Try to extract from HTML body (strip HTML tags first)
|
|
1411
|
+
if (message.htmlBody) {
|
|
1412
|
+
const plainText = stripHtml(message.htmlBody);
|
|
1413
|
+
const otp = extractFromText(plainText);
|
|
1414
|
+
if (otp)
|
|
1415
|
+
return otp;
|
|
1416
|
+
}
|
|
1417
|
+
// 3. Try to extract from message parts
|
|
1418
|
+
if (message.parts && message.parts.length > 0) {
|
|
1419
|
+
for (const part of message.parts) {
|
|
1420
|
+
if (part.body) {
|
|
1421
|
+
const otp = extractFromText(part.body);
|
|
1422
|
+
if (otp)
|
|
1423
|
+
return otp;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
// 4. Fallback: try to extract from subject
|
|
1428
|
+
const otp = extractFromText(message.subject);
|
|
1429
|
+
if (otp)
|
|
1430
|
+
return otp;
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Gets the text content from a message for debugging/display purposes
|
|
1435
|
+
* @param message - The message to extract text from
|
|
1436
|
+
* @returns string - The text content of the message
|
|
1437
|
+
*/
|
|
1438
|
+
static getMessageTextContent(message) {
|
|
1439
|
+
// Helper function to strip HTML tags
|
|
1440
|
+
const stripHtml = (html) => {
|
|
1441
|
+
return html.replace(/<[^>]*>/g, '').replace(/&[^;]+;/g, ' ');
|
|
1442
|
+
};
|
|
1443
|
+
// Priority order: textBody > htmlBody > parts > subject
|
|
1444
|
+
if (message.textBody) {
|
|
1445
|
+
return message.textBody;
|
|
1446
|
+
}
|
|
1447
|
+
if (message.htmlBody) {
|
|
1448
|
+
return stripHtml(message.htmlBody);
|
|
1449
|
+
}
|
|
1450
|
+
if (message.parts && message.parts.length > 0) {
|
|
1451
|
+
return message.parts.map(part => part.body || '').join('\n');
|
|
1452
|
+
}
|
|
1453
|
+
return message.subject || '';
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Fetches messages from all inboxes in the domain
|
|
1457
|
+
* @param limit - Number of messages to fetch (default: 50)
|
|
1458
|
+
* @param sort - Sort order: 'ascending' or 'descending' (default: 'descending')
|
|
1459
|
+
* @param full - Whether to fetch full message content (default: false)
|
|
1460
|
+
* @returns Promise<MailinatorMessage[]> - Array of messages from all inboxes
|
|
1461
|
+
*/
|
|
1462
|
+
static async fetchAllInboxMessages(limit = 50, sort = 'descending', full = false) {
|
|
1463
|
+
try {
|
|
1464
|
+
const url = `https://api.mailinator.com/v2/domains/${MAILINATOR_DOMAIN}/inboxes/?limit=${limit}&sort=${sort}${full ? '&full=true' : ''}`;
|
|
1465
|
+
const response = await fetch(url, {
|
|
1466
|
+
method: 'GET',
|
|
1467
|
+
headers: {
|
|
1468
|
+
'Authorization': `Bearer ${MAILINATOR_API_KEY}`,
|
|
1469
|
+
'Content-Type': 'application/json'
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
if (!response.ok) {
|
|
1473
|
+
throw new Error(`Mailinator API error: ${response.status} ${response.statusText}`);
|
|
1474
|
+
}
|
|
1475
|
+
const data = await response.json();
|
|
1476
|
+
return data.msgs || data.messages || data || [];
|
|
1477
|
+
}
|
|
1478
|
+
catch (error) {
|
|
1479
|
+
console.error('Error fetching messages from all inboxes:', error);
|
|
1480
|
+
throw error;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Waits for an OTP to arrive in the inbox and extracts it
|
|
1485
|
+
* @param inbox - The inbox name to monitor (optional - if not provided, searches all inboxes)
|
|
1486
|
+
* @param timeout - Maximum time to wait in milliseconds (default: 30000)
|
|
1487
|
+
* @param checkInterval - How often to check in milliseconds (default: 1000)
|
|
1488
|
+
* @param checkRecentMessagesSinceMs - When > 0, check messages from the last X milliseconds and return the most recent OTP (default: 0)
|
|
1489
|
+
* @returns Promise<string | null> - The extracted OTP code or null if timeout/no OTP found
|
|
1490
|
+
*/
|
|
1491
|
+
static async waitForOTP(inbox, timeout = 30000, checkInterval = 1000, checkRecentMessagesSinceMs = 0) {
|
|
1492
|
+
const startTime = Date.now();
|
|
1493
|
+
// If checkRecentMessagesSinceMs > 0, check for recent messages first
|
|
1494
|
+
if (checkRecentMessagesSinceMs > 0) {
|
|
1495
|
+
console.log(`Checking for OTP in messages from the last ${checkRecentMessagesSinceMs}ms...`);
|
|
1496
|
+
const recentMessagesCutoff = Date.now() - checkRecentMessagesSinceMs;
|
|
1497
|
+
const recentMessages = inbox
|
|
1498
|
+
? await OTP.fetchLastMessages(inbox)
|
|
1499
|
+
: await OTP.fetchAllInboxMessages();
|
|
1500
|
+
// Filter messages from the specified time window
|
|
1501
|
+
const messagesFromWindow = recentMessages.filter(msg => msg.time >= recentMessagesCutoff);
|
|
1502
|
+
// Sort by time (most recent first) and check for OTP
|
|
1503
|
+
const sortedMessages = messagesFromWindow.sort((a, b) => b.time - a.time);
|
|
1504
|
+
for (const message of sortedMessages) {
|
|
1505
|
+
let otp = OTP.extractOTPFromMessage(message);
|
|
1506
|
+
if (otp) {
|
|
1507
|
+
console.log(`Found OTP in recent message: ${otp}`);
|
|
1508
|
+
return otp;
|
|
1509
|
+
}
|
|
1510
|
+
// If no OTP found in summary, fetch full message details
|
|
1511
|
+
try {
|
|
1512
|
+
const fullMessage = await OTP.fetchMessage(message.id);
|
|
1513
|
+
otp = OTP.extractOTPFromMessage(fullMessage);
|
|
1514
|
+
if (otp) {
|
|
1515
|
+
console.log(`Found OTP in recent full message: ${otp}`);
|
|
1516
|
+
return otp;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
catch (error) {
|
|
1520
|
+
console.warn(`Error fetching full message ${message.id}:`, error);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
console.log(`No OTP found in recent messages, starting to monitor for new messages...`);
|
|
1524
|
+
}
|
|
1525
|
+
// Get initial messages for monitoring new ones (from specific inbox or all inboxes)
|
|
1526
|
+
const initialMessages = inbox
|
|
1527
|
+
? await OTP.fetchLastMessages(inbox)
|
|
1528
|
+
: await OTP.fetchAllInboxMessages();
|
|
1529
|
+
const initialMessageIds = new Set(initialMessages.map(msg => msg.id));
|
|
1530
|
+
while (Date.now() - startTime < timeout) {
|
|
1531
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
1532
|
+
try {
|
|
1533
|
+
// Get current messages (from specific inbox or all inboxes)
|
|
1534
|
+
const currentMessages = inbox
|
|
1535
|
+
? await OTP.fetchLastMessages(inbox)
|
|
1536
|
+
: await OTP.fetchAllInboxMessages();
|
|
1537
|
+
const newMessages = currentMessages.filter(msg => !initialMessageIds.has(msg.id));
|
|
1538
|
+
if (newMessages.length > 0) {
|
|
1539
|
+
// Get the first new message and try to extract OTP
|
|
1540
|
+
const newMessage = newMessages[0];
|
|
1541
|
+
// First try to extract OTP from the message summary (faster)
|
|
1542
|
+
let otp = OTP.extractOTPFromMessage(newMessage);
|
|
1543
|
+
if (otp) {
|
|
1544
|
+
return otp;
|
|
1545
|
+
}
|
|
1546
|
+
// If no OTP found in summary, fetch full message details
|
|
1547
|
+
const fullMessage = await OTP.fetchMessage(newMessage.id);
|
|
1548
|
+
otp = OTP.extractOTPFromMessage(fullMessage);
|
|
1549
|
+
if (otp) {
|
|
1550
|
+
return otp;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
catch (error) {
|
|
1555
|
+
console.warn('Error checking for new messages:', error);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return null; // Timeout reached or no OTP found
|
|
1559
|
+
}
|
|
1372
1560
|
}
|
|
1373
1561
|
|
|
1374
|
-
var pRetryExports = requirePRetry();
|
|
1375
|
-
var pRetry = /*@__PURE__*/getDefaultExportFromCjs(pRetryExports);
|
|
1376
|
-
|
|
1377
1562
|
class ProboPlaywright {
|
|
1378
1563
|
constructor(config = DEFAULT_PLAYWRIGHT_TIMEOUT_CONFIG, page = null) {
|
|
1379
1564
|
this.page = null;
|
|
@@ -1466,6 +1651,17 @@ class ProboPlaywright {
|
|
|
1466
1651
|
case PlaywrightAction.SET_SLIDER:
|
|
1467
1652
|
await this.setSliderValue(locator, argument);
|
|
1468
1653
|
break;
|
|
1654
|
+
case PlaywrightAction.WAIT_FOR_OTP:
|
|
1655
|
+
// till we figure out how to get the inbox name we will wait for ANY OTP in all inboxes
|
|
1656
|
+
const otp = await OTP.waitForOTP();
|
|
1657
|
+
if (otp) {
|
|
1658
|
+
console.log(`✅ OTP found: ${otp}`);
|
|
1659
|
+
await locator.fill(otp);
|
|
1660
|
+
}
|
|
1661
|
+
else {
|
|
1662
|
+
console.log(`❌ OTP not found`);
|
|
1663
|
+
}
|
|
1664
|
+
break;
|
|
1469
1665
|
case PlaywrightAction.ASSERT_CONTAINS_VALUE:
|
|
1470
1666
|
const containerText = await this.getTextValue(locator);
|
|
1471
1667
|
if (!matchRegex(containerText, argument)) {
|
|
@@ -2437,5 +2633,5 @@ class Probo {
|
|
|
2437
2633
|
}
|
|
2438
2634
|
}
|
|
2439
2635
|
|
|
2440
|
-
export { Highlighter, NavTracker, PlaywrightAction, Probo, ProboLogLevel, ProboPlaywright, findClosestVisibleElement };
|
|
2636
|
+
export { Highlighter, NavTracker, OTP, PlaywrightAction, Probo, ProboLogLevel, ProboPlaywright, findClosestVisibleElement };
|
|
2441
2637
|
//# sourceMappingURL=index.js.map
|