@intuned/browser-dev 0.1.4-dev.1 → 0.1.5-dev.1
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/README.md +0 -1
- package/dist/ai/export.d.ts +1 -1
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/isPageLoaded.js +14 -3
- package/dist/ai/tests/testIsPageLoaded.spec.js +3 -3
- package/dist/helpers/downloadFile.js +37 -0
- package/dist/helpers/export.d.ts +10 -7
- package/dist/helpers/frame_utils/constants.js +8 -0
- package/dist/helpers/frame_utils/findAllIframes.js +79 -0
- package/dist/helpers/frame_utils/getContainerFrame.js +22 -0
- package/dist/helpers/frame_utils/index.js +44 -0
- package/dist/helpers/frame_utils/tests/testFindAllIframes.spec.js +170 -0
- package/dist/helpers/gotoUrl.js +1 -1
- package/dist/helpers/index.d.ts +10 -7
- package/dist/helpers/index.js +0 -19
- package/dist/helpers/tests/testDownloadFile.spec.js +41 -6
- package/dist/helpers/tests/testInjectAttachmentType.spec.js +482 -0
- package/dist/helpers/tests/testValidateDataUsingSchema.spec.js +35 -31
- package/dist/helpers/tests/testWithDomSettledWait.spec.js +119 -0
- package/dist/helpers/types/Attachment.js +11 -6
- package/dist/helpers/types/index.js +1 -20
- package/dist/helpers/uploadFileToS3.js +2 -2
- package/dist/helpers/validateDataUsingSchema.js +30 -71
- package/dist/helpers/waitForDomSettled.js +57 -40
- package/dist/intunedServices/ApiGateway/tests/testApiGateway.spec.js +4 -4
- package/dist/optimized-extractors/listExtractionHelpers/__tests__/testArrayExtractorFromPage.spec.js +271 -2
- package/dist/optimized-extractors/listExtractionHelpers/runAiExtraction.js +55 -8
- package/generated-docs/ai/functions/extractStructuredData.mdx +5 -5
- package/generated-docs/ai/functions/isPageLoaded.mdx +1 -0
- package/generated-docs/helpers/functions/clickButtonAndWait.mdx +63 -0
- package/generated-docs/helpers/functions/clickUntilExhausted.mdx +112 -0
- package/generated-docs/helpers/functions/scrollToLoadContent.mdx +1 -7
- package/generated-docs/helpers/functions/validateDataUsingSchema.mdx +5 -5
- package/how-to-generate-docs.md +1 -0
- package/package.json +2 -2
- package/dist/helpers/types/CustomTypeRegistry.js +0 -48
package/README.md
CHANGED
|
@@ -50,7 +50,6 @@ The Intuned Browser SDK provides a comprehensive set of tools for browser automa
|
|
|
50
50
|
|
|
51
51
|
- **Schema Validation** - Validate data structures with `validateDataUsingSchema()`
|
|
52
52
|
- **Empty Value Filtering** - Filter empty values with `filterEmptyValues()`
|
|
53
|
-
- **Custom Type Registry** - Define custom validators with `CustomTypeRegistry`
|
|
54
53
|
|
|
55
54
|
### ⚡ Optimized Extractors
|
|
56
55
|
|
package/dist/ai/export.d.ts
CHANGED
|
@@ -535,7 +535,7 @@ export type SUPPORTED_MODELS =
|
|
|
535
535
|
* @param {Object} input - Input object containing the page to check
|
|
536
536
|
* @param {Page} input.page - The Playwright page to check
|
|
537
537
|
* @param {number} [input.timeoutInMs=10000] - Screenshot timeout in milliseconds. Defaults to 10000
|
|
538
|
-
* @param {SUPPORTED_MODELS} [input.model="gpt-
|
|
538
|
+
* @param {SUPPORTED_MODELS} [input.model="gpt-5-mini-2025-08-07"] - AI model to use for the check. See [SUPPORTED_MODELS](../type-aliases/SUPPORTED_MODELS) for all supported models. Defaults to "gpt-5-mini-2025-08-07"
|
|
539
539
|
* @param {string} [input.apiKey] - Optional API key for the AI service (if provided, will not be billed to your account)
|
|
540
540
|
* @returns {Promise<boolean>} Promise resolving to true if page is loaded, false if still loading
|
|
541
541
|
* @example
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -535,7 +535,7 @@ export type SUPPORTED_MODELS =
|
|
|
535
535
|
* @param {Object} input - Input object containing the page to check
|
|
536
536
|
* @param {Page} input.page - The Playwright page to check
|
|
537
537
|
* @param {number} [input.timeoutInMs=10000] - Screenshot timeout in milliseconds. Defaults to 10000
|
|
538
|
-
* @param {SUPPORTED_MODELS} [input.model="gpt-
|
|
538
|
+
* @param {SUPPORTED_MODELS} [input.model="gpt-5-mini-2025-08-07"] - AI model to use for the check. See [SUPPORTED_MODELS](../type-aliases/SUPPORTED_MODELS) for all supported models. Defaults to "gpt-5-mini-2025-08-07"
|
|
539
539
|
* @param {string} [input.apiKey] - Optional API key for the AI service (if provided, will not be billed to your account)
|
|
540
540
|
* @returns {Promise<boolean>} Promise resolving to true if page is loaded, false if still loading
|
|
541
541
|
* @example
|
package/dist/ai/isPageLoaded.js
CHANGED
|
@@ -20,7 +20,7 @@ const isPageLoaded = async options => {
|
|
|
20
20
|
});
|
|
21
21
|
const gateway = _utils.GatewayFactory.createAIGateway({
|
|
22
22
|
apiKey: options === null || options === void 0 ? void 0 : options.apiKey,
|
|
23
|
-
model: (options === null || options === void 0 ? void 0 : options.model) ?? "gpt-
|
|
23
|
+
model: (options === null || options === void 0 ? void 0 : options.model) ?? "gpt-5-mini-2025-08-07"
|
|
24
24
|
});
|
|
25
25
|
const gatewayModel = await gateway.getModel();
|
|
26
26
|
const base64Image = Buffer.from(screenshotBytes).toString("base64");
|
|
@@ -30,11 +30,22 @@ const isPageLoaded = async options => {
|
|
|
30
30
|
role: "system",
|
|
31
31
|
content: `You are a helpful assistant that determines if a webpage finished loading. If the page finished loading, start your answer with 'True'. If the page is loading, start your answer with 'False'. If you are not sure, start your answer with 'Dont know'. In a new line, add a reason to your response.
|
|
32
32
|
|
|
33
|
-
Some good cues for determining if a page is loading:
|
|
33
|
+
## Some good cues for determining if a page is loading (return 'False'):
|
|
34
34
|
- Loading spinner
|
|
35
35
|
- Page is blank
|
|
36
36
|
- Some content looks like it's missing
|
|
37
|
-
- Not on splash screen
|
|
37
|
+
- Not on splash screen
|
|
38
|
+
|
|
39
|
+
## Special cases for LOADED pages (return 'True')
|
|
40
|
+
- CAPTCHA challenges are considered loaded, because it is indeed loaded and waiting for the user to solve the captcha.
|
|
41
|
+
- Bot detection screens (e.g., "Checking your browser", Cloudflare verification) are considered loaded, because it is indeed loaded and waiting for the user to solve the captcha.
|
|
42
|
+
- Static error pages (404 Not Found, 403 Forbidden, 500 Internal Server Error, etc.)
|
|
43
|
+
- Login/authentication screens
|
|
44
|
+
- Complete forms ready for user input
|
|
45
|
+
- Fully rendered articles, product pages, or dashboards
|
|
46
|
+
- Cookie consent banners or popups over complete content
|
|
47
|
+
- Payment or checkout pages with all fields visible
|
|
48
|
+
`
|
|
38
49
|
}, {
|
|
39
50
|
role: "user",
|
|
40
51
|
content: [{
|
|
@@ -175,7 +175,7 @@ const ERROR_PAGE = `
|
|
|
175
175
|
</body>
|
|
176
176
|
</html>
|
|
177
177
|
`;
|
|
178
|
-
|
|
178
|
+
_extendedTest.describe.skip("isPageLoaded Tests", () => {
|
|
179
179
|
let browser;
|
|
180
180
|
let page;
|
|
181
181
|
(0, _extendedTest.beforeAll)(async () => {
|
|
@@ -263,11 +263,11 @@ const ERROR_PAGE = `
|
|
|
263
263
|
});
|
|
264
264
|
});
|
|
265
265
|
(0, _extendedTest.describe)("Different model types", () => {
|
|
266
|
-
(0, _extendedTest.test)("should work with claude-3-
|
|
266
|
+
(0, _extendedTest.test)("should work with claude-3-7-sonnet-latest", async () => {
|
|
267
267
|
await page.setContent(FULLY_LOADED_PAGE);
|
|
268
268
|
const result = await (0, _isPageLoaded.isPageLoaded)({
|
|
269
269
|
page,
|
|
270
|
-
model: "claude-3-
|
|
270
|
+
model: "claude-3-7-sonnet-latest",
|
|
271
271
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
272
272
|
timeoutInMs: 10000
|
|
273
273
|
});
|
|
@@ -62,6 +62,7 @@ const downloadFile = async input => {
|
|
|
62
62
|
downloadPromise = pageToDownloadFrom.waitForEvent("download", {
|
|
63
63
|
timeout: (timeoutInMs ?? 5000) + 1000
|
|
64
64
|
});
|
|
65
|
+
await page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()");
|
|
65
66
|
if (isUrlTrigger(trigger)) {
|
|
66
67
|
absoluteUrl = await getAbsoluteUrl(page, trigger);
|
|
67
68
|
if (!isValidURL(absoluteUrl)) {
|
|
@@ -92,8 +93,44 @@ const downloadFile = async input => {
|
|
|
92
93
|
}
|
|
93
94
|
} else if (isCallableTrigger(trigger)) {
|
|
94
95
|
action = await trigger(page);
|
|
96
|
+
try {
|
|
97
|
+
await pageToDownloadFrom.waitForFunction("window.waitForPrintDialog", undefined, {
|
|
98
|
+
timeout: 1000
|
|
99
|
+
});
|
|
100
|
+
const pdf = await pageToDownloadFrom.pdf({
|
|
101
|
+
format: "A4"
|
|
102
|
+
});
|
|
103
|
+
const pdfBase64 = pdf.toString("base64");
|
|
104
|
+
await pageToDownloadFrom.evaluate(base64 => {
|
|
105
|
+
const dataUrl = `data:application/pdf;base64,${base64}`;
|
|
106
|
+
const link = document.createElement("a");
|
|
107
|
+
link.href = dataUrl;
|
|
108
|
+
link.download = "print.pdf";
|
|
109
|
+
document.body.appendChild(link);
|
|
110
|
+
link.click();
|
|
111
|
+
document.body.removeChild(link);
|
|
112
|
+
}, pdfBase64);
|
|
113
|
+
} catch (error) {}
|
|
95
114
|
} else if (isLocatorTrigger(trigger)) {
|
|
96
115
|
action = await trigger.click();
|
|
116
|
+
try {
|
|
117
|
+
await pageToDownloadFrom.waitForFunction("window.waitForPrintDialog", undefined, {
|
|
118
|
+
timeout: 1000
|
|
119
|
+
});
|
|
120
|
+
const pdf = await pageToDownloadFrom.pdf({
|
|
121
|
+
format: "A4"
|
|
122
|
+
});
|
|
123
|
+
const pdfBase64 = pdf.toString("base64");
|
|
124
|
+
await pageToDownloadFrom.evaluate(base64 => {
|
|
125
|
+
const dataUrl = `data:application/pdf;base64,${base64}`;
|
|
126
|
+
const link = document.createElement("a");
|
|
127
|
+
link.href = dataUrl;
|
|
128
|
+
link.download = "print.pdf";
|
|
129
|
+
document.body.appendChild(link);
|
|
130
|
+
link.click();
|
|
131
|
+
document.body.removeChild(link);
|
|
132
|
+
}, pdfBase64);
|
|
133
|
+
} catch (error) {}
|
|
97
134
|
}
|
|
98
135
|
const download = await downloadPromise;
|
|
99
136
|
if ((0, _utils.isGenerateCodeMode)()) {
|
package/dist/helpers/export.d.ts
CHANGED
|
@@ -204,7 +204,7 @@ export declare function filterEmptyValues<T>(input: { data: T }): T;
|
|
|
204
204
|
* @param {string} [input.waitForLoadState="load"] - When to consider navigation succeeded. Options: "load", "domcontentloaded", "networkidle", "commit". Defaults to "load"
|
|
205
205
|
* @param {boolean} [input.throwOnTimeout=true] - Whether to throw an error if navigation times out. When false, the function returns without throwing, allowing continued execution. Defaults to true.
|
|
206
206
|
* @param {boolean} [input.waitForLoadingStateUsingAi=false] - When true, uses AI vision to verify the page is fully loaded by checking for loading spinners, blank content, or incomplete states. Retries up to 4 times with 5-second delays. Defaults to false
|
|
207
|
-
* @param {SUPPORTED_MODELS} [input.model="gpt-
|
|
207
|
+
* @param {SUPPORTED_MODELS} [input.model="gpt-5-mini-2025-08-07"] - AI model to use for loading verification. See [SUPPORTED_MODELS](../type-aliases/SUPPORTED_MODELS) for all supported models. Defaults to "gpt-5-mini-2025-08-07"
|
|
208
208
|
* @param {string} [input.apiKey] - Optional API key for the AI service (if provided, will not be billed to your account)
|
|
209
209
|
* @returns {Promise<void>} Promise that resolves when navigation completes successfully. If the operation fails and `throwOnTimeout` is false, resolves without error
|
|
210
210
|
*
|
|
@@ -540,7 +540,7 @@ export declare function uploadFileToS3(input: {
|
|
|
540
540
|
* @param {Object} input - The input object containing data and schema
|
|
541
541
|
* @param {Record<string, any> | Record<string, any>[]} input.data - The data to validate. Can be a single data object or an array of data objects
|
|
542
542
|
* @param {Record<string, any>} input.schema - JSON schema object defining validation rules
|
|
543
|
-
* @returns {
|
|
543
|
+
* @returns {void} Returns nothing if validation passes, throws ValidationError if it fails
|
|
544
544
|
*
|
|
545
545
|
* @example
|
|
546
546
|
* ```typescript Basic User Data Validation
|
|
@@ -562,7 +562,7 @@ export declare function uploadFileToS3(input: {
|
|
|
562
562
|
* }
|
|
563
563
|
* };
|
|
564
564
|
*
|
|
565
|
-
*
|
|
565
|
+
* validateDataUsingSchema({ data: userData, schema: userSchema });
|
|
566
566
|
* // Validation passes, no error thrown
|
|
567
567
|
* }
|
|
568
568
|
* ```
|
|
@@ -585,7 +585,7 @@ export declare function uploadFileToS3(input: {
|
|
|
585
585
|
* }
|
|
586
586
|
* };
|
|
587
587
|
*
|
|
588
|
-
*
|
|
588
|
+
* validateDataUsingSchema({ data: userData, schema: userSchema });
|
|
589
589
|
* // Validation fails, throws ValidationError
|
|
590
590
|
* }
|
|
591
591
|
* ```
|
|
@@ -593,7 +593,7 @@ export declare function uploadFileToS3(input: {
|
|
|
593
593
|
export declare function validateDataUsingSchema(input: {
|
|
594
594
|
data: Record<string, any>[] | Record<string, any>;
|
|
595
595
|
schema: Record<string, any>;
|
|
596
|
-
}):
|
|
596
|
+
}): void;
|
|
597
597
|
|
|
598
598
|
/**
|
|
599
599
|
* Executes a callback function and waits for network requests to settle before returning.
|
|
@@ -839,6 +839,9 @@ export interface Attachment {
|
|
|
839
839
|
/** The name/key of the file in the S3 bucket */
|
|
840
840
|
fileName: string;
|
|
841
841
|
|
|
842
|
+
/** The S3 object key/path */
|
|
843
|
+
key: string;
|
|
844
|
+
|
|
842
845
|
/** The S3 bucket name where the file is stored */
|
|
843
846
|
bucket: string;
|
|
844
847
|
|
|
@@ -846,13 +849,13 @@ export interface Attachment {
|
|
|
846
849
|
region: string;
|
|
847
850
|
|
|
848
851
|
/** Optional custom S3 endpoint URL. Defaults to undefined for standard AWS S3 */
|
|
849
|
-
endpoint?: string;
|
|
852
|
+
endpoint?: string | null;
|
|
850
853
|
|
|
851
854
|
/** A human-readable filename suggestion for downloads or display */
|
|
852
855
|
suggestedFileName: string;
|
|
853
856
|
|
|
854
857
|
/** The file type of the file */
|
|
855
|
-
fileType?: AttachmentType;
|
|
858
|
+
fileType?: AttachmentType | null;
|
|
856
859
|
|
|
857
860
|
/**
|
|
858
861
|
* Returns a JSON-serializable record representation of the file.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.IFRAME_TAGS = exports.ALL_IFRAMES_CSS_SELECTOR = void 0;
|
|
7
|
+
const IFRAME_TAGS = exports.IFRAME_TAGS = ["iframe", "frame"];
|
|
8
|
+
const ALL_IFRAMES_CSS_SELECTOR = exports.ALL_IFRAMES_CSS_SELECTOR = IFRAME_TAGS.join(", ");
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.findAllIframes = findAllIframes;
|
|
7
|
+
exports.findAllIframesList = findAllIframesList;
|
|
8
|
+
var _Logger = require("../../common/Logger");
|
|
9
|
+
var _constants = require("./constants");
|
|
10
|
+
async function findAllIframes(root, iframeTimeoutMs = 10000) {
|
|
11
|
+
const processed = new Set();
|
|
12
|
+
return await processFrameRecursive(root, processed, iframeTimeoutMs);
|
|
13
|
+
}
|
|
14
|
+
async function findAllIframesList(root, iframeTimeoutMs = 10000) {
|
|
15
|
+
const iframeNodes = await findAllIframes(root, iframeTimeoutMs);
|
|
16
|
+
return flattenIframeTree(iframeNodes);
|
|
17
|
+
}
|
|
18
|
+
async function processFrameRecursive(root, processedRoots, iframeTimeoutMs) {
|
|
19
|
+
if (processedRoots.has(root)) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
processedRoots.add(root);
|
|
23
|
+
const iframeNodes = [];
|
|
24
|
+
try {
|
|
25
|
+
const iframeLocator = root.locator(_constants.ALL_IFRAMES_CSS_SELECTOR);
|
|
26
|
+
let iframeCount;
|
|
27
|
+
try {
|
|
28
|
+
iframeCount = await Promise.race([iframeLocator.count(), new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), iframeTimeoutMs))]);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
_Logger.logger.error("Timeout counting iframes in context, skipping");
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
for (let i = 0; i < iframeCount; i++) {
|
|
34
|
+
try {
|
|
35
|
+
const processSingleIframe = async index => {
|
|
36
|
+
const iframeElementLocator = iframeLocator.nth(index);
|
|
37
|
+
const iframeElement = await iframeElementLocator.elementHandle();
|
|
38
|
+
if (!iframeElement) {
|
|
39
|
+
_Logger.logger.error(`Could not get element handle for iframe: ${iframeElement}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const contentFrame = await iframeElement.contentFrame();
|
|
43
|
+
if (!contentFrame) {
|
|
44
|
+
_Logger.logger.error(`Could not access content_frame for iframe: ${iframeElement}`);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const nestedIframes = await processFrameRecursive(contentFrame, processedRoots, iframeTimeoutMs);
|
|
48
|
+
return {
|
|
49
|
+
frame: contentFrame,
|
|
50
|
+
nestedIframes: nestedIframes
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const iframeNode = await Promise.race([processSingleIframe(i), new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), iframeTimeoutMs))]);
|
|
54
|
+
if (iframeNode !== null) {
|
|
55
|
+
iframeNodes.push(iframeNode);
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
_Logger.logger.error(`Timeout processing iframe ${i} in context, skipping`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
_Logger.logger.error(`Error processing frames in context: ${error}`);
|
|
64
|
+
}
|
|
65
|
+
return iframeNodes;
|
|
66
|
+
}
|
|
67
|
+
function flattenIframeTree(iframeNodes) {
|
|
68
|
+
const flattened = [];
|
|
69
|
+
function flattenRecursive(nodes) {
|
|
70
|
+
for (const node of nodes) {
|
|
71
|
+
flattened.push(node);
|
|
72
|
+
if (node.nestedIframes && node.nestedIframes.length > 0) {
|
|
73
|
+
flattenRecursive(node.nestedIframes);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
flattenRecursive(iframeNodes);
|
|
78
|
+
return flattened;
|
|
79
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getContainerFrame = getContainerFrame;
|
|
7
|
+
async function getContainerFrame(element) {
|
|
8
|
+
let handle;
|
|
9
|
+
if ("elementHandle" in element) {
|
|
10
|
+
handle = await element.elementHandle();
|
|
11
|
+
} else {
|
|
12
|
+
handle = element;
|
|
13
|
+
}
|
|
14
|
+
if (!handle) {
|
|
15
|
+
throw new Error("Could not get element handle");
|
|
16
|
+
}
|
|
17
|
+
const frame = await handle.ownerFrame();
|
|
18
|
+
if (!frame) {
|
|
19
|
+
throw new Error("Could not get owner frame for element");
|
|
20
|
+
}
|
|
21
|
+
return frame;
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "ALL_IFRAMES_CSS_SELECTOR", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _constants.ALL_IFRAMES_CSS_SELECTOR;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "IFRAME_TAGS", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _constants.IFRAME_TAGS;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "IframeNode", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _findAllIframes.IframeNode;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "findAllIframes", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _findAllIframes.findAllIframes;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "findAllIframesList", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () {
|
|
33
|
+
return _findAllIframes.findAllIframesList;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(exports, "getContainerFrame", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _getContainerFrame.getContainerFrame;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
var _findAllIframes = require("./findAllIframes");
|
|
43
|
+
var _getContainerFrame = require("./getContainerFrame");
|
|
44
|
+
var _constants = require("./constants");
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _extendedTest = require("../../../common/extendedTest");
|
|
4
|
+
var _playwrightCore = require("playwright-core");
|
|
5
|
+
var _findAllIframes = require("../findAllIframes");
|
|
6
|
+
(0, _extendedTest.describe)("Test findAllIframes", () => {
|
|
7
|
+
let browser;
|
|
8
|
+
let page;
|
|
9
|
+
(0, _extendedTest.beforeAll)(async () => {
|
|
10
|
+
browser = await _playwrightCore.chromium.launch({
|
|
11
|
+
headless: true
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
(0, _extendedTest.afterAll)(async () => {
|
|
15
|
+
await browser.close();
|
|
16
|
+
});
|
|
17
|
+
(0, _extendedTest.beforeEach)(async () => {
|
|
18
|
+
page = await browser.newPage();
|
|
19
|
+
});
|
|
20
|
+
(0, _extendedTest.afterEach)(async () => {
|
|
21
|
+
await page.close();
|
|
22
|
+
});
|
|
23
|
+
(0, _extendedTest.test)("should find all iframes - basic", async () => {
|
|
24
|
+
await page.goto(`data:text/html,
|
|
25
|
+
<html>
|
|
26
|
+
<body>
|
|
27
|
+
<h1>Main Content</h1>
|
|
28
|
+
<iframe id="iframe-1"
|
|
29
|
+
src="data:text/html,<html><body><h2>Iframe 1</h2></body></html>"
|
|
30
|
+
width="300"
|
|
31
|
+
height="200">
|
|
32
|
+
</iframe>
|
|
33
|
+
<iframe id="iframe-2"
|
|
34
|
+
src="data:text/html,<html><body><h2>Iframe 2</h2></body></html>"
|
|
35
|
+
width="300"
|
|
36
|
+
height="200">
|
|
37
|
+
</iframe>
|
|
38
|
+
</body>
|
|
39
|
+
</html>`, {
|
|
40
|
+
waitUntil: "domcontentloaded"
|
|
41
|
+
});
|
|
42
|
+
const iframeNodes = await (0, _findAllIframes.findAllIframes)(page);
|
|
43
|
+
(0, _extendedTest.expect)(iframeNodes.length).toBe(2);
|
|
44
|
+
(0, _extendedTest.expect)(iframeNodes[0].frame).toBeDefined();
|
|
45
|
+
(0, _extendedTest.expect)(iframeNodes[1].frame).toBeDefined();
|
|
46
|
+
(0, _extendedTest.expect)(iframeNodes[0].nestedIframes.length).toBe(0);
|
|
47
|
+
(0, _extendedTest.expect)(iframeNodes[1].nestedIframes.length).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
(0, _extendedTest.test)("should find nested iframes", async () => {
|
|
50
|
+
await page.goto(`data:text/html,
|
|
51
|
+
<html>
|
|
52
|
+
<body>
|
|
53
|
+
<h1>Main Content</h1>
|
|
54
|
+
<iframe id="outer-iframe"
|
|
55
|
+
src="data:text/html,<html><body><h2>Outer</h2><iframe src='data:text/html,<html><body><h3>Inner</h3></body></html>'></iframe></body></html>"
|
|
56
|
+
width="400"
|
|
57
|
+
height="300">
|
|
58
|
+
</iframe>
|
|
59
|
+
</body>
|
|
60
|
+
</html>`, {
|
|
61
|
+
waitUntil: "domcontentloaded"
|
|
62
|
+
});
|
|
63
|
+
const iframeNodes = await (0, _findAllIframes.findAllIframes)(page);
|
|
64
|
+
(0, _extendedTest.expect)(iframeNodes.length).toBe(1);
|
|
65
|
+
(0, _extendedTest.expect)(iframeNodes[0].nestedIframes.length).toBe(1);
|
|
66
|
+
(0, _extendedTest.expect)(iframeNodes[0].nestedIframes[0].nestedIframes.length).toBe(0);
|
|
67
|
+
});
|
|
68
|
+
(0, _extendedTest.test)("should handle page with no iframes", async () => {
|
|
69
|
+
await page.goto(`data:text/html,
|
|
70
|
+
<html>
|
|
71
|
+
<body>
|
|
72
|
+
<h1>Main Content</h1>
|
|
73
|
+
<p>No iframes here</p>
|
|
74
|
+
</body>
|
|
75
|
+
</html>`, {
|
|
76
|
+
waitUntil: "domcontentloaded"
|
|
77
|
+
});
|
|
78
|
+
const iframeNodes = await (0, _findAllIframes.findAllIframes)(page);
|
|
79
|
+
(0, _extendedTest.expect)(iframeNodes.length).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
(0, _extendedTest.test)("should handle problematic iframe sources", async () => {
|
|
82
|
+
await page.goto(`data:text/html,
|
|
83
|
+
<html>
|
|
84
|
+
<body>
|
|
85
|
+
<h1>Main Content</h1>
|
|
86
|
+
<iframe id="javascript-iframe"
|
|
87
|
+
src="javascript:"
|
|
88
|
+
width="300"
|
|
89
|
+
height="200">
|
|
90
|
+
</iframe>
|
|
91
|
+
<iframe id="blob-iframe"
|
|
92
|
+
src="blob:null/invalid"
|
|
93
|
+
width="300"
|
|
94
|
+
height="200">
|
|
95
|
+
</iframe>
|
|
96
|
+
<iframe id="about-blank-iframe"
|
|
97
|
+
src="about:blank"
|
|
98
|
+
width="300"
|
|
99
|
+
height="200">
|
|
100
|
+
</iframe>
|
|
101
|
+
</body>
|
|
102
|
+
</html>`, {
|
|
103
|
+
waitUntil: "domcontentloaded",
|
|
104
|
+
timeout: 5000
|
|
105
|
+
});
|
|
106
|
+
const startTime = Date.now();
|
|
107
|
+
const iframeNodes = await (0, _findAllIframes.findAllIframes)(page, 2000);
|
|
108
|
+
const elapsedTime = Date.now() - startTime;
|
|
109
|
+
(0, _extendedTest.expect)(elapsedTime).toBeLessThan(10000);
|
|
110
|
+
(0, _extendedTest.expect)(iframeNodes.length).toBeLessThan(3);
|
|
111
|
+
});
|
|
112
|
+
(0, _extendedTest.test)("should find iframes with srcdoc attribute", async () => {
|
|
113
|
+
await page.goto(`data:text/html,
|
|
114
|
+
<html>
|
|
115
|
+
<body>
|
|
116
|
+
<h1>Main Content</h1>
|
|
117
|
+
<iframe id="srcdoc-iframe"
|
|
118
|
+
srcdoc="<html><body><h2>Srcdoc Content</h2></body></html>"
|
|
119
|
+
width="300"
|
|
120
|
+
height="200">
|
|
121
|
+
</iframe>
|
|
122
|
+
</body>
|
|
123
|
+
</html>`, {
|
|
124
|
+
waitUntil: "domcontentloaded"
|
|
125
|
+
});
|
|
126
|
+
const iframeNodes = await (0, _findAllIframes.findAllIframes)(page);
|
|
127
|
+
(0, _extendedTest.expect)(iframeNodes.length).toBe(1);
|
|
128
|
+
});
|
|
129
|
+
(0, _extendedTest.test)("should handle legacy frame elements", async () => {
|
|
130
|
+
await page.goto(`data:text/html,
|
|
131
|
+
<html>
|
|
132
|
+
<head><title>Legacy Frameset</title></head>
|
|
133
|
+
<frameset cols="50%,50%">
|
|
134
|
+
<frame id="frame-1"
|
|
135
|
+
src="data:text/html,<html><body><h2>Frame 1</h2></body></html>">
|
|
136
|
+
</frame>
|
|
137
|
+
<frame id="frame-2"
|
|
138
|
+
src="data:text/html,<html><body><h2>Frame 2</h2></body></html>">
|
|
139
|
+
</frame>
|
|
140
|
+
</frameset>
|
|
141
|
+
</html>`, {
|
|
142
|
+
waitUntil: "domcontentloaded"
|
|
143
|
+
});
|
|
144
|
+
const iframeNodes = await (0, _findAllIframes.findAllIframes)(page);
|
|
145
|
+
(0, _extendedTest.expect)(iframeNodes.length).toBeGreaterThanOrEqual(0);
|
|
146
|
+
for (const node of iframeNodes) {
|
|
147
|
+
(0, _extendedTest.expect)(node.frame).toBeDefined();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
(0, _extendedTest.test)("findAllIframesList should return flat list", async () => {
|
|
151
|
+
await page.goto(`data:text/html,
|
|
152
|
+
<html>
|
|
153
|
+
<body>
|
|
154
|
+
<h1>Main Content</h1>
|
|
155
|
+
<iframe id="outer-iframe"
|
|
156
|
+
src="data:text/html,<html><body><h2>Outer</h2><iframe id='inner-iframe' src='data:text/html,<html><body><h3>Inner</h3></body></html>'></iframe></body></html>"
|
|
157
|
+
width="400"
|
|
158
|
+
height="300">
|
|
159
|
+
</iframe>
|
|
160
|
+
</body>
|
|
161
|
+
</html>`, {
|
|
162
|
+
waitUntil: "domcontentloaded"
|
|
163
|
+
});
|
|
164
|
+
const iframeList = await (0, _findAllIframes.findAllIframesList)(page);
|
|
165
|
+
(0, _extendedTest.expect)(iframeList.length).toBe(2);
|
|
166
|
+
for (const node of iframeList) {
|
|
167
|
+
(0, _extendedTest.expect)(node.frame).toBeDefined();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
package/dist/helpers/gotoUrl.js
CHANGED
package/dist/helpers/index.d.ts
CHANGED
|
@@ -204,7 +204,7 @@ export declare function filterEmptyValues<T>(input: { data: T }): T;
|
|
|
204
204
|
* @param {string} [input.waitForLoadState="load"] - When to consider navigation succeeded. Options: "load", "domcontentloaded", "networkidle", "commit". Defaults to "load"
|
|
205
205
|
* @param {boolean} [input.throwOnTimeout=true] - Whether to throw an error if navigation times out. When false, the function returns without throwing, allowing continued execution. Defaults to true.
|
|
206
206
|
* @param {boolean} [input.waitForLoadingStateUsingAi=false] - When true, uses AI vision to verify the page is fully loaded by checking for loading spinners, blank content, or incomplete states. Retries up to 4 times with 5-second delays. Defaults to false
|
|
207
|
-
* @param {SUPPORTED_MODELS} [input.model="gpt-
|
|
207
|
+
* @param {SUPPORTED_MODELS} [input.model="gpt-5-mini-2025-08-07"] - AI model to use for loading verification. See [SUPPORTED_MODELS](../type-aliases/SUPPORTED_MODELS) for all supported models. Defaults to "gpt-5-mini-2025-08-07"
|
|
208
208
|
* @param {string} [input.apiKey] - Optional API key for the AI service (if provided, will not be billed to your account)
|
|
209
209
|
* @returns {Promise<void>} Promise that resolves when navigation completes successfully. If the operation fails and `throwOnTimeout` is false, resolves without error
|
|
210
210
|
*
|
|
@@ -540,7 +540,7 @@ export declare function uploadFileToS3(input: {
|
|
|
540
540
|
* @param {Object} input - The input object containing data and schema
|
|
541
541
|
* @param {Record<string, any> | Record<string, any>[]} input.data - The data to validate. Can be a single data object or an array of data objects
|
|
542
542
|
* @param {Record<string, any>} input.schema - JSON schema object defining validation rules
|
|
543
|
-
* @returns {
|
|
543
|
+
* @returns {void} Returns nothing if validation passes, throws ValidationError if it fails
|
|
544
544
|
*
|
|
545
545
|
* @example
|
|
546
546
|
* ```typescript Basic User Data Validation
|
|
@@ -562,7 +562,7 @@ export declare function uploadFileToS3(input: {
|
|
|
562
562
|
* }
|
|
563
563
|
* };
|
|
564
564
|
*
|
|
565
|
-
*
|
|
565
|
+
* validateDataUsingSchema({ data: userData, schema: userSchema });
|
|
566
566
|
* // Validation passes, no error thrown
|
|
567
567
|
* }
|
|
568
568
|
* ```
|
|
@@ -585,7 +585,7 @@ export declare function uploadFileToS3(input: {
|
|
|
585
585
|
* }
|
|
586
586
|
* };
|
|
587
587
|
*
|
|
588
|
-
*
|
|
588
|
+
* validateDataUsingSchema({ data: userData, schema: userSchema });
|
|
589
589
|
* // Validation fails, throws ValidationError
|
|
590
590
|
* }
|
|
591
591
|
* ```
|
|
@@ -593,7 +593,7 @@ export declare function uploadFileToS3(input: {
|
|
|
593
593
|
export declare function validateDataUsingSchema(input: {
|
|
594
594
|
data: Record<string, any>[] | Record<string, any>;
|
|
595
595
|
schema: Record<string, any>;
|
|
596
|
-
}):
|
|
596
|
+
}): void;
|
|
597
597
|
|
|
598
598
|
/**
|
|
599
599
|
* Executes a callback function and waits for network requests to settle before returning.
|
|
@@ -839,6 +839,9 @@ export interface Attachment {
|
|
|
839
839
|
/** The name/key of the file in the S3 bucket */
|
|
840
840
|
fileName: string;
|
|
841
841
|
|
|
842
|
+
/** The S3 object key/path */
|
|
843
|
+
key: string;
|
|
844
|
+
|
|
842
845
|
/** The S3 bucket name where the file is stored */
|
|
843
846
|
bucket: string;
|
|
844
847
|
|
|
@@ -846,13 +849,13 @@ export interface Attachment {
|
|
|
846
849
|
region: string;
|
|
847
850
|
|
|
848
851
|
/** Optional custom S3 endpoint URL. Defaults to undefined for standard AWS S3 */
|
|
849
|
-
endpoint?: string;
|
|
852
|
+
endpoint?: string | null;
|
|
850
853
|
|
|
851
854
|
/** A human-readable filename suggestion for downloads or display */
|
|
852
855
|
suggestedFileName: string;
|
|
853
856
|
|
|
854
857
|
/** The file type of the file */
|
|
855
|
-
fileType?: AttachmentType;
|
|
858
|
+
fileType?: AttachmentType | null;
|
|
856
859
|
|
|
857
860
|
/**
|
|
858
861
|
* Returns a JSON-serializable record representation of the file.
|
package/dist/helpers/index.js
CHANGED
|
@@ -3,24 +3,6 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
Object.defineProperty(exports, "AttachmentValidator", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: function () {
|
|
9
|
-
return _types.AttachmentValidator;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
Object.defineProperty(exports, "CustomTypeRegistry", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _types.CustomTypeRegistry;
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
Object.defineProperty(exports, "CustomTypeValidator", {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
get: function () {
|
|
21
|
-
return _types.CustomTypeValidator;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
6
|
Object.defineProperty(exports, "clickUntilExhausted", {
|
|
25
7
|
enumerable: true,
|
|
26
8
|
get: function () {
|
|
@@ -109,7 +91,6 @@ var _gotoUrl = require("./gotoUrl");
|
|
|
109
91
|
var _uploadFileToS = require("./uploadFileToS3");
|
|
110
92
|
var _withNetworkSettledWait = require("./withNetworkSettledWait");
|
|
111
93
|
var _validateDataUsingSchema = require("./validateDataUsingSchema");
|
|
112
|
-
var _types = require("./types");
|
|
113
94
|
var _downloadFile = require("./downloadFile");
|
|
114
95
|
var _filterEmptyValues = require("./filterEmptyValues");
|
|
115
96
|
var _processDate = require("./processDate");
|