@intuned/browser-dev 0.1.5-dev.0 → 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/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/tests/testWithDomSettledWait.spec.js +119 -0
- package/dist/helpers/waitForDomSettled.js +57 -40
- package/dist/optimized-extractors/listExtractionHelpers/__tests__/testArrayExtractorFromPage.spec.js +271 -2
- package/package.json +2 -2
|
@@ -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
|
+
});
|
|
@@ -161,4 +161,123 @@ var _waitForDomSettled = require("../waitForDomSettled");
|
|
|
161
161
|
const content = await page.textContent("#target");
|
|
162
162
|
(0, _extendedTest.expect)(content).toContain("Locator source content");
|
|
163
163
|
});
|
|
164
|
+
(0, _extendedTest.test)("should wait for DOM to settle with iframe dynamic content", async () => {
|
|
165
|
+
await page.goto(`
|
|
166
|
+
data:text/html,
|
|
167
|
+
<html>
|
|
168
|
+
<body>
|
|
169
|
+
<h1>Main Page</h1>
|
|
170
|
+
<iframe id="test-iframe"
|
|
171
|
+
src="data:text/html,<html><body><div id='iframe-content'>Initial content</div></body></html>"
|
|
172
|
+
width="400"
|
|
173
|
+
height="300">
|
|
174
|
+
</iframe>
|
|
175
|
+
</body>
|
|
176
|
+
</html>
|
|
177
|
+
`);
|
|
178
|
+
await page.waitForTimeout(500);
|
|
179
|
+
const iframeElement = await page.locator("#test-iframe").elementHandle();
|
|
180
|
+
const iframeFrame = await (iframeElement === null || iframeElement === void 0 ? void 0 : iframeElement.contentFrame());
|
|
181
|
+
if (!iframeFrame) {
|
|
182
|
+
throw new Error("Could not get iframe frame");
|
|
183
|
+
}
|
|
184
|
+
await iframeFrame.evaluate(() => {
|
|
185
|
+
let counter = 0;
|
|
186
|
+
const intervalId = setInterval(() => {
|
|
187
|
+
counter++;
|
|
188
|
+
const div = document.createElement("div");
|
|
189
|
+
div.textContent = `Dynamic content ${counter}`;
|
|
190
|
+
div.className = "dynamic-item";
|
|
191
|
+
document.body.appendChild(div);
|
|
192
|
+
if (counter >= 3) {
|
|
193
|
+
clearInterval(intervalId);
|
|
194
|
+
}
|
|
195
|
+
}, 200);
|
|
196
|
+
});
|
|
197
|
+
const settled = await (0, _waitForDomSettled.waitForDomSettled)({
|
|
198
|
+
source: page,
|
|
199
|
+
settleDurationMs: 1000,
|
|
200
|
+
timeoutInMs: 10000
|
|
201
|
+
});
|
|
202
|
+
(0, _extendedTest.expect)(settled).toBe(true);
|
|
203
|
+
const dynamicItems = await iframeFrame.locator(".dynamic-item").count();
|
|
204
|
+
(0, _extendedTest.expect)(dynamicItems).toBe(3);
|
|
205
|
+
for (let i = 0; i < 3; i++) {
|
|
206
|
+
const content = await iframeFrame.locator(".dynamic-item").nth(i).textContent();
|
|
207
|
+
(0, _extendedTest.expect)(content).toBe(`Dynamic content ${i + 1}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
(0, _extendedTest.test)("should wait for DOM to settle with nested iframes", async () => {
|
|
211
|
+
await page.goto(`
|
|
212
|
+
data:text/html,
|
|
213
|
+
<html>
|
|
214
|
+
<body>
|
|
215
|
+
<h1>Main Page</h1>
|
|
216
|
+
<div id="main-content">Main content</div>
|
|
217
|
+
<iframe id="outer-iframe"
|
|
218
|
+
src="data:text/html,<html><body><h2>Outer iframe</h2><div id='outer-content'>Outer initial</div><iframe id='inner-iframe' src='data:text/html,<html><body><h3>Inner iframe</h3><div id=inner-content>Inner initial</div></body></html>'></iframe></body></html>"
|
|
219
|
+
width="500"
|
|
220
|
+
height="400">
|
|
221
|
+
</iframe>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
224
|
+
`);
|
|
225
|
+
await page.waitForTimeout(1000);
|
|
226
|
+
const outerIframeElement = await page.locator("#outer-iframe").elementHandle();
|
|
227
|
+
const outerFrame = await (outerIframeElement === null || outerIframeElement === void 0 ? void 0 : outerIframeElement.contentFrame());
|
|
228
|
+
if (!outerFrame) {
|
|
229
|
+
throw new Error("Could not get outer frame");
|
|
230
|
+
}
|
|
231
|
+
const innerIframeElement = await outerFrame.locator("#inner-iframe").elementHandle();
|
|
232
|
+
const innerFrame = await (innerIframeElement === null || innerIframeElement === void 0 ? void 0 : innerIframeElement.contentFrame());
|
|
233
|
+
if (!innerFrame) {
|
|
234
|
+
throw new Error("Could not get inner frame");
|
|
235
|
+
}
|
|
236
|
+
await outerFrame.evaluate(() => {
|
|
237
|
+
let outerCounter = 0;
|
|
238
|
+
const outerInterval = setInterval(() => {
|
|
239
|
+
outerCounter++;
|
|
240
|
+
const div = document.createElement("div");
|
|
241
|
+
div.textContent = `Outer dynamic ${outerCounter}`;
|
|
242
|
+
div.className = "outer-dynamic";
|
|
243
|
+
const outerContent = document.getElementById("outer-content");
|
|
244
|
+
outerContent === null || outerContent === void 0 || outerContent.appendChild(div);
|
|
245
|
+
if (outerCounter >= 2) {
|
|
246
|
+
clearInterval(outerInterval);
|
|
247
|
+
}
|
|
248
|
+
}, 150);
|
|
249
|
+
});
|
|
250
|
+
await innerFrame.evaluate(() => {
|
|
251
|
+
let innerCounter = 0;
|
|
252
|
+
const innerInterval = setInterval(() => {
|
|
253
|
+
innerCounter++;
|
|
254
|
+
const div = document.createElement("div");
|
|
255
|
+
div.textContent = `Inner dynamic ${innerCounter}`;
|
|
256
|
+
div.className = "inner-dynamic";
|
|
257
|
+
const innerContent = document.getElementById("inner-content");
|
|
258
|
+
innerContent === null || innerContent === void 0 || innerContent.appendChild(div);
|
|
259
|
+
if (innerCounter >= 2) {
|
|
260
|
+
clearInterval(innerInterval);
|
|
261
|
+
}
|
|
262
|
+
}, 200);
|
|
263
|
+
});
|
|
264
|
+
const settled = await (0, _waitForDomSettled.waitForDomSettled)({
|
|
265
|
+
source: page,
|
|
266
|
+
settleDurationMs: 1000,
|
|
267
|
+
timeoutInMs: 15000
|
|
268
|
+
});
|
|
269
|
+
(0, _extendedTest.expect)(settled).toBe(true);
|
|
270
|
+
const outerDynamicItems = await outerFrame.locator(".outer-dynamic").count();
|
|
271
|
+
(0, _extendedTest.expect)(outerDynamicItems).toBe(2);
|
|
272
|
+
const innerDynamicItems = await innerFrame.locator(".inner-dynamic").count();
|
|
273
|
+
(0, _extendedTest.expect)(innerDynamicItems).toBe(2);
|
|
274
|
+
for (let i = 0; i < 2; i++) {
|
|
275
|
+
const content = await outerFrame.locator(".outer-dynamic").nth(i).textContent();
|
|
276
|
+
(0, _extendedTest.expect)(content).toBe(`Outer dynamic ${i + 1}`);
|
|
277
|
+
}
|
|
278
|
+
for (let i = 0; i < 2; i++) {
|
|
279
|
+
const content = await innerFrame.locator(".inner-dynamic").nth(i).textContent();
|
|
280
|
+
(0, _extendedTest.expect)(content).toBe(`Inner dynamic ${i + 1}`);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
164
283
|
});
|
|
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.waitForDomSettled = void 0;
|
|
7
7
|
var _locatorHelpers = require("../common/locatorHelpers");
|
|
8
8
|
var _Logger = require("../common/Logger");
|
|
9
|
+
var _findAllIframes = require("./frame_utils/findAllIframes");
|
|
10
|
+
var _getContainerFrame = require("./frame_utils/getContainerFrame");
|
|
9
11
|
const waitForDomSettled = async options => {
|
|
10
12
|
const {
|
|
11
13
|
source,
|
|
@@ -14,10 +16,10 @@ const waitForDomSettled = async options => {
|
|
|
14
16
|
} = options;
|
|
15
17
|
const settleDurationMsFloored = Math.floor(settleDurationMs);
|
|
16
18
|
const timeoutMs = Math.floor(timeoutInMs);
|
|
17
|
-
let
|
|
19
|
+
let frame;
|
|
18
20
|
let elementHandle;
|
|
19
21
|
if (!(0, _locatorHelpers.isPage)(source)) {
|
|
20
|
-
|
|
22
|
+
frame = await (0, _getContainerFrame.getContainerFrame)(source);
|
|
21
23
|
const handle = await source.elementHandle();
|
|
22
24
|
if (!handle) {
|
|
23
25
|
_Logger.logger.warn("Could not get element handle from locator");
|
|
@@ -25,46 +27,26 @@ const waitForDomSettled = async options => {
|
|
|
25
27
|
}
|
|
26
28
|
elementHandle = handle;
|
|
27
29
|
} else if ((0, _locatorHelpers.isPage)(source)) {
|
|
28
|
-
|
|
29
|
-
elementHandle = await
|
|
30
|
+
frame = source.mainFrame();
|
|
31
|
+
elementHandle = await frame.evaluateHandle("document.documentElement");
|
|
30
32
|
} else {
|
|
31
33
|
throw new Error("Invalid state");
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
target,
|
|
35
|
+
const jsCode = (target, args) => {
|
|
36
|
+
const {
|
|
36
37
|
settleDurationMsFloored,
|
|
37
38
|
timeoutMs
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
mutationTimer = window.setTimeout(() => {
|
|
50
|
-
settled = true;
|
|
51
|
-
observer.disconnect();
|
|
52
|
-
clearTimeout(timeoutTimer);
|
|
53
|
-
resolve(true);
|
|
54
|
-
}, settleDurationMsFloored);
|
|
55
|
-
});
|
|
56
|
-
const timeoutTimer = window.setTimeout(() => {
|
|
57
|
-
settled = true;
|
|
58
|
-
observer.disconnect();
|
|
59
|
-
clearTimeout(mutationTimer);
|
|
60
|
-
reject(new Error(`DOM timed out settling after ${timeoutMs} ms`));
|
|
61
|
-
}, timeoutMs);
|
|
62
|
-
observer.observe(target, {
|
|
63
|
-
childList: true,
|
|
64
|
-
subtree: true,
|
|
65
|
-
attributes: true,
|
|
66
|
-
characterData: true
|
|
67
|
-
});
|
|
39
|
+
} = args;
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
if (!target) {
|
|
42
|
+
reject(new Error("Target element not found"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
let mutationTimer;
|
|
46
|
+
let settled = false;
|
|
47
|
+
const observer = new MutationObserver(() => {
|
|
48
|
+
if (settled) return;
|
|
49
|
+
clearTimeout(mutationTimer);
|
|
68
50
|
mutationTimer = window.setTimeout(() => {
|
|
69
51
|
settled = true;
|
|
70
52
|
observer.disconnect();
|
|
@@ -72,12 +54,47 @@ const waitForDomSettled = async options => {
|
|
|
72
54
|
resolve(true);
|
|
73
55
|
}, settleDurationMsFloored);
|
|
74
56
|
});
|
|
75
|
-
|
|
76
|
-
|
|
57
|
+
const timeoutTimer = window.setTimeout(() => {
|
|
58
|
+
settled = true;
|
|
59
|
+
observer.disconnect();
|
|
60
|
+
clearTimeout(mutationTimer);
|
|
61
|
+
reject(new Error(`DOM timed out settling after ${timeoutMs} ms`));
|
|
62
|
+
}, timeoutMs);
|
|
63
|
+
observer.observe(target, {
|
|
64
|
+
childList: true,
|
|
65
|
+
subtree: true,
|
|
66
|
+
attributes: true,
|
|
67
|
+
characterData: true
|
|
68
|
+
});
|
|
69
|
+
mutationTimer = window.setTimeout(() => {
|
|
70
|
+
settled = true;
|
|
71
|
+
observer.disconnect();
|
|
72
|
+
clearTimeout(timeoutTimer);
|
|
73
|
+
resolve(true);
|
|
74
|
+
}, settleDurationMsFloored);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
const result = await elementHandle.evaluate(jsCode, {
|
|
77
79
|
settleDurationMsFloored,
|
|
78
80
|
timeoutMs
|
|
79
81
|
});
|
|
80
|
-
|
|
82
|
+
if (!result) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const allIframes = await (0, _findAllIframes.findAllIframesList)(frame);
|
|
86
|
+
for (const iframeNode of allIframes) {
|
|
87
|
+
const iframeElementHandle = await iframeNode.frame.evaluateHandle("document.documentElement");
|
|
88
|
+
const iframeResult = await iframeElementHandle.evaluate(jsCode, {
|
|
89
|
+
settleDurationMsFloored,
|
|
90
|
+
timeoutMs
|
|
91
|
+
});
|
|
92
|
+
await iframeElementHandle.dispose();
|
|
93
|
+
if (!iframeResult) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
81
98
|
} catch (error) {
|
|
82
99
|
_Logger.logger.warn(`DOM settlement detection failed: ${error}`);
|
|
83
100
|
return false;
|
package/dist/optimized-extractors/listExtractionHelpers/__tests__/testArrayExtractorFromPage.spec.js
CHANGED
|
@@ -69,10 +69,10 @@ _extendedTest.describe.skip("Array Extractor from Page Caching Tests", () => {
|
|
|
69
69
|
label: testLabel,
|
|
70
70
|
itemEntitySchema,
|
|
71
71
|
strategy: {
|
|
72
|
-
model: "claude-3-5-
|
|
72
|
+
model: "claude-3-5-haiku-20241022",
|
|
73
73
|
type: "HTML"
|
|
74
74
|
},
|
|
75
|
-
variantKey
|
|
75
|
+
variantKey,
|
|
76
76
|
apiKey: process.env.ANTHROPIC_API_KEY
|
|
77
77
|
};
|
|
78
78
|
await page.setContent(productListTemplate);
|
|
@@ -126,5 +126,274 @@ _extendedTest.describe.skip("Array Extractor from Page Caching Tests", () => {
|
|
|
126
126
|
(0, _extendedTest.expect)(fourthResult[0]).toHaveProperty("price", "$1099");
|
|
127
127
|
console.log("All cache behavior tests completed successfully!");
|
|
128
128
|
});
|
|
129
|
+
(0, _extendedTest.test)("should demonstrate caching behavior with different types of DOM changes", async ({
|
|
130
|
+
page
|
|
131
|
+
}) => {
|
|
132
|
+
await page.goto("https://vendor.myfloridamarketplace.com/search/bids/detail/9507", {
|
|
133
|
+
timeout: 0
|
|
134
|
+
});
|
|
135
|
+
const result = await (0, _.extractArrayFromPage)(page, {
|
|
136
|
+
label: "external website links.",
|
|
137
|
+
itemEntityName: "downloadable_links",
|
|
138
|
+
itemEntitySchema: {
|
|
139
|
+
type: "object",
|
|
140
|
+
required: ["anchor_innerText"],
|
|
141
|
+
properties: {
|
|
142
|
+
anchor_href: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: "extract all downloadable files hrefs."
|
|
145
|
+
},
|
|
146
|
+
anchor_innerText: {
|
|
147
|
+
primary: true,
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "extract title attribute of that anchor.k"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
strategy: {
|
|
154
|
+
type: "HTML",
|
|
155
|
+
model: "claude-3-5-haiku-20241022"
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
console.log("Result:", result);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
(0, _extendedTest.describe)("Edge Cases - Item Count Variations", () => {
|
|
162
|
+
(0, _extendedTest.test)("should handle extraction with 0 items", async ({
|
|
163
|
+
page
|
|
164
|
+
}) => {
|
|
165
|
+
const testLabel = `product-list-0-items-${(0, _uuid.v4)()}`;
|
|
166
|
+
const emptyListTemplate = `
|
|
167
|
+
<div class="products-container">
|
|
168
|
+
<div class="additional-info">
|
|
169
|
+
<div class="shipping-notice">No products available</div>
|
|
170
|
+
<div class="return-policy">Check back later</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
`;
|
|
174
|
+
const itemEntitySchema = {
|
|
175
|
+
type: "object",
|
|
176
|
+
required: ["title", "price"],
|
|
177
|
+
properties: {
|
|
178
|
+
title: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "Product title",
|
|
181
|
+
primary: true
|
|
182
|
+
},
|
|
183
|
+
price: {
|
|
184
|
+
type: "string",
|
|
185
|
+
description: "Product price"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
await page.setContent(emptyListTemplate);
|
|
190
|
+
const result = await (0, _.extractArrayFromPage)(page, {
|
|
191
|
+
itemEntityName: "product",
|
|
192
|
+
label: testLabel,
|
|
193
|
+
itemEntitySchema,
|
|
194
|
+
strategy: {
|
|
195
|
+
model: "claude-3-5-haiku-20241022",
|
|
196
|
+
type: "HTML"
|
|
197
|
+
},
|
|
198
|
+
variantKey: testLabel,
|
|
199
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
200
|
+
});
|
|
201
|
+
console.log("0 items result:", result);
|
|
202
|
+
(0, _extendedTest.expect)(result).toHaveLength(0);
|
|
203
|
+
(0, _extendedTest.expect)(Array.isArray(result)).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
(0, _extendedTest.test)("should handle extraction with 1 item", async ({
|
|
206
|
+
page
|
|
207
|
+
}) => {
|
|
208
|
+
const testLabel = `product-list-1-item-${(0, _uuid.v4)()}`;
|
|
209
|
+
const singleItemTemplate = `
|
|
210
|
+
<div class="products-container">
|
|
211
|
+
<div class="product-item">
|
|
212
|
+
<h2 class="product-title">MacBook Pro M3</h2>
|
|
213
|
+
<div class="price-wrapper">
|
|
214
|
+
<span class="price">$2499</span>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="details">
|
|
217
|
+
<p class="product-description">Professional laptop with M3 Max chip</p>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="additional-info">
|
|
221
|
+
<div class="shipping-notice">Free express shipping</div>
|
|
222
|
+
<div class="return-policy">30-day return policy</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
`;
|
|
226
|
+
const itemEntitySchema = {
|
|
227
|
+
type: "object",
|
|
228
|
+
required: ["title", "price"],
|
|
229
|
+
properties: {
|
|
230
|
+
title: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Product title",
|
|
233
|
+
primary: true
|
|
234
|
+
},
|
|
235
|
+
price: {
|
|
236
|
+
type: "string",
|
|
237
|
+
description: "Product price"
|
|
238
|
+
},
|
|
239
|
+
description: {
|
|
240
|
+
type: "string",
|
|
241
|
+
description: "Product description"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
await page.setContent(singleItemTemplate);
|
|
246
|
+
const result = await (0, _.extractArrayFromPage)(page, {
|
|
247
|
+
itemEntityName: "product",
|
|
248
|
+
label: testLabel,
|
|
249
|
+
itemEntitySchema,
|
|
250
|
+
strategy: {
|
|
251
|
+
model: "claude-3-5-haiku-20241022",
|
|
252
|
+
type: "HTML"
|
|
253
|
+
},
|
|
254
|
+
variantKey: testLabel,
|
|
255
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
256
|
+
});
|
|
257
|
+
console.log("1 item result:", result);
|
|
258
|
+
(0, _extendedTest.expect)(result).toHaveLength(1);
|
|
259
|
+
(0, _extendedTest.expect)(result[0]).toHaveProperty("title", "MacBook Pro M3");
|
|
260
|
+
(0, _extendedTest.expect)(result[0]).toHaveProperty("price", "$2499");
|
|
261
|
+
(0, _extendedTest.expect)(result[0]).toHaveProperty("description", "Professional laptop with M3 Max chip");
|
|
262
|
+
});
|
|
263
|
+
(0, _extendedTest.test)("should handle extraction with 2 items", async ({
|
|
264
|
+
page
|
|
265
|
+
}) => {
|
|
266
|
+
const testLabel = `product-list-2-items-${(0, _uuid.v4)()}`;
|
|
267
|
+
const twoItemsTemplate = `
|
|
268
|
+
<div class="products-container">
|
|
269
|
+
<div class="product-item">
|
|
270
|
+
<h2 class="product-title">iPad Pro</h2>
|
|
271
|
+
<div class="price-wrapper">
|
|
272
|
+
<span class="price">$1099</span>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="details">
|
|
275
|
+
<p class="product-description">Powerful tablet with M2 chip</p>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
<div class="product-item">
|
|
279
|
+
<h2 class="product-title">Apple Watch Ultra</h2>
|
|
280
|
+
<div class="price-wrapper">
|
|
281
|
+
<span class="price">$799</span>
|
|
282
|
+
</div>
|
|
283
|
+
<div class="details">
|
|
284
|
+
<p class="product-description">Rugged smartwatch for athletes</p>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
<div class="additional-info">
|
|
288
|
+
<div class="shipping-notice">Free shipping on all orders</div>
|
|
289
|
+
<div class="return-policy">30-day return policy</div>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
`;
|
|
293
|
+
const itemEntitySchema = {
|
|
294
|
+
type: "object",
|
|
295
|
+
required: ["title", "price"],
|
|
296
|
+
properties: {
|
|
297
|
+
title: {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: "Product title",
|
|
300
|
+
primary: true
|
|
301
|
+
},
|
|
302
|
+
price: {
|
|
303
|
+
type: "string",
|
|
304
|
+
description: "Product price"
|
|
305
|
+
},
|
|
306
|
+
description: {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: "Product description"
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
await page.setContent(twoItemsTemplate);
|
|
313
|
+
const result = await (0, _.extractArrayFromPage)(page, {
|
|
314
|
+
itemEntityName: "product",
|
|
315
|
+
label: testLabel,
|
|
316
|
+
itemEntitySchema,
|
|
317
|
+
strategy: {
|
|
318
|
+
model: "claude-3-5-haiku-20241022",
|
|
319
|
+
type: "HTML"
|
|
320
|
+
},
|
|
321
|
+
variantKey: testLabel,
|
|
322
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
323
|
+
});
|
|
324
|
+
console.log("2 items result:", result);
|
|
325
|
+
(0, _extendedTest.expect)(result).toHaveLength(2);
|
|
326
|
+
(0, _extendedTest.expect)(result[0]).toHaveProperty("title", "iPad Pro");
|
|
327
|
+
(0, _extendedTest.expect)(result[0]).toHaveProperty("price", "$1099");
|
|
328
|
+
(0, _extendedTest.expect)(result[0]).toHaveProperty("description", "Powerful tablet with M2 chip");
|
|
329
|
+
(0, _extendedTest.expect)(result[1]).toHaveProperty("title", "Apple Watch Ultra");
|
|
330
|
+
(0, _extendedTest.expect)(result[1]).toHaveProperty("price", "$799");
|
|
331
|
+
(0, _extendedTest.expect)(result[1]).toHaveProperty("description", "Rugged smartwatch for athletes");
|
|
332
|
+
});
|
|
333
|
+
(0, _extendedTest.test)("should cache and reuse results for 1 item correctly", async ({
|
|
334
|
+
page
|
|
335
|
+
}) => {
|
|
336
|
+
const testLabel = `product-list-1-item-cache-${(0, _uuid.v4)()}`;
|
|
337
|
+
const singleItemTemplate = `
|
|
338
|
+
<div class="products-container">
|
|
339
|
+
<div class="product-item">
|
|
340
|
+
<h2 class="product-title">Sony WH-1000XM5</h2>
|
|
341
|
+
<div class="price-wrapper">
|
|
342
|
+
<span class="price">$399</span>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="details">
|
|
345
|
+
<p class="product-description">Premium noise-canceling headphones</p>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
`;
|
|
350
|
+
const itemEntitySchema = {
|
|
351
|
+
type: "object",
|
|
352
|
+
required: ["title", "price"],
|
|
353
|
+
properties: {
|
|
354
|
+
title: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description: "Product title",
|
|
357
|
+
primary: true
|
|
358
|
+
},
|
|
359
|
+
price: {
|
|
360
|
+
type: "string",
|
|
361
|
+
description: "Product price"
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
const extractionOptions = {
|
|
366
|
+
itemEntityName: "product",
|
|
367
|
+
label: testLabel,
|
|
368
|
+
itemEntitySchema,
|
|
369
|
+
strategy: {
|
|
370
|
+
model: "claude-3-5-haiku-20241022",
|
|
371
|
+
type: "HTML"
|
|
372
|
+
},
|
|
373
|
+
variantKey: testLabel,
|
|
374
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
375
|
+
};
|
|
376
|
+
await page.setContent(singleItemTemplate);
|
|
377
|
+
const firstResult = await (0, _.extractArrayFromPage)(page, extractionOptions);
|
|
378
|
+
console.log("First extraction (1 item):", firstResult);
|
|
379
|
+
(0, _extendedTest.expect)(firstResult).toHaveLength(1);
|
|
380
|
+
(0, _extendedTest.expect)(firstResult[0]).toHaveProperty("title", "Sony WH-1000XM5");
|
|
381
|
+
(0, _extendedTest.expect)(firstResult[0]).toHaveProperty("price", "$399");
|
|
382
|
+
await page.setContent(singleItemTemplate);
|
|
383
|
+
const secondResult = await (0, _.extractArrayFromPage)(page, extractionOptions);
|
|
384
|
+
console.log("Second extraction (from cache, 1 item):", secondResult);
|
|
385
|
+
(0, _extendedTest.expect)(secondResult).toEqual(firstResult);
|
|
386
|
+
(0, _extendedTest.expect)(secondResult).toHaveLength(1);
|
|
387
|
+
(0, _extendedTest.expect)(secondResult[0]).toHaveProperty("title", "Sony WH-1000XM5");
|
|
388
|
+
(0, _extendedTest.expect)(secondResult[0]).toHaveProperty("price", "$399");
|
|
389
|
+
const modifiedTemplate = singleItemTemplate.replace("Sony WH-1000XM5", "Bose QuietComfort Ultra").replace("$399", "$429");
|
|
390
|
+
await page.setContent(modifiedTemplate);
|
|
391
|
+
const thirdResult = await (0, _.extractArrayFromPage)(page, extractionOptions);
|
|
392
|
+
console.log("Third extraction (changed content, 1 item):", thirdResult);
|
|
393
|
+
(0, _extendedTest.expect)(thirdResult).not.toEqual(firstResult);
|
|
394
|
+
(0, _extendedTest.expect)(thirdResult).toHaveLength(1);
|
|
395
|
+
(0, _extendedTest.expect)(thirdResult[0]).toHaveProperty("title", "Bose QuietComfort Ultra");
|
|
396
|
+
(0, _extendedTest.expect)(thirdResult[0]).toHaveProperty("price", "$429");
|
|
397
|
+
});
|
|
129
398
|
});
|
|
130
399
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intuned/browser-dev",
|
|
3
|
-
"version": "0.1.5-dev.
|
|
3
|
+
"version": "0.1.5-dev.1",
|
|
4
4
|
"description": "runner package for intuned functions",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"typesVersions": {
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"openai": "4.77.3",
|
|
77
77
|
"stack-utils": "2.0.6",
|
|
78
78
|
"tslib": "2.6.0",
|
|
79
|
-
"uuid": "
|
|
79
|
+
"uuid": "11.0.0",
|
|
80
80
|
"zod": "^3.25.76",
|
|
81
81
|
"zod-to-json-schema": "^3.24.6",
|
|
82
82
|
"zod-validation-error": "3.0.3"
|