@sudobility/testomniac_runner 0.0.149 → 0.0.153
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/bun.lock
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"@noble/curves": "^1.0.0",
|
|
9
9
|
"@noble/hashes": "^1.0.0",
|
|
10
10
|
"@sudobility/signic_sdk": "^0.1.7",
|
|
11
|
-
"@sudobility/testomniac_runner_service": "^0.1.
|
|
12
|
-
"@sudobility/testomniac_types": "^0.0.
|
|
11
|
+
"@sudobility/testomniac_runner_service": "^0.1.146",
|
|
12
|
+
"@sudobility/testomniac_types": "^0.0.77",
|
|
13
13
|
"hono": "^4.7.0",
|
|
14
14
|
"jose": "^6.1.2",
|
|
15
15
|
"openai": "^6.7.0",
|
|
@@ -172,9 +172,9 @@
|
|
|
172
172
|
|
|
173
173
|
"@sudobility/signic_sdk": ["@sudobility/signic_sdk@0.1.7", "", {}, "sha512-5XSgHSVsmyrMQ/ui1nDywwzt9dbRCsaeJ5tX6mKw2ZXbTZ82OsMr+dqDyV9XV3pfy7IHRIZq73af5KBamx72Fw=="],
|
|
174
174
|
|
|
175
|
-
"@sudobility/testomniac_runner_service": ["@sudobility/testomniac_runner_service@0.1.
|
|
175
|
+
"@sudobility/testomniac_runner_service": ["@sudobility/testomniac_runner_service@0.1.146", "", { "peerDependencies": { "@sudobility/testomniac_types": "^0.0.77" } }, "sha512-8Qi90GLDrSzjIyelCFXYZc22dZcQHQQxTmILc9DYUyPlFDd5L1PZ46eYG1ESoY4FXHTQBRWHLIQ12JHVOMCWqQ=="],
|
|
176
176
|
|
|
177
|
-
"@sudobility/testomniac_types": ["@sudobility/testomniac_types@0.0.
|
|
177
|
+
"@sudobility/testomniac_types": ["@sudobility/testomniac_types@0.0.77", "", { "peerDependencies": { "@sudobility/types": "^1.9.62" } }, "sha512-Jj+caqdxsl9NvzNK5IF/WtmTQQhM2On+z5bOIdNm1OjnzEqWwvxoDRUQGsxEXC2Ymo1nfoJdc1wxNJCWcUSSdA=="],
|
|
178
178
|
|
|
179
179
|
"@sudobility/types": ["@sudobility/types@1.9.61", "", {}, "sha512-SODGpstB/iKfK3H/4BvJx/FBcc1h3gutUjGotyxN19VnOfWyzaDoEmW7eyoxOAYhZyXMXagSiii+NIEZvuxKog=="],
|
|
180
180
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/testomniac_runner",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.153",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"@noble/curves": "^1.0.0",
|
|
25
25
|
"@noble/hashes": "^1.0.0",
|
|
26
26
|
"@sudobility/signic_sdk": "^0.1.7",
|
|
27
|
-
"@sudobility/testomniac_runner_service": "^0.1.
|
|
28
|
-
"@sudobility/testomniac_types": "^0.0.
|
|
27
|
+
"@sudobility/testomniac_runner_service": "^0.1.146",
|
|
28
|
+
"@sudobility/testomniac_types": "^0.0.77",
|
|
29
29
|
"hono": "^4.7.0",
|
|
30
30
|
"jose": "^6.1.2",
|
|
31
31
|
"openai": "^6.7.0",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
2
|
+
import { createServer, type Server } from "node:http";
|
|
3
|
+
import type { Browser } from "puppeteer-core";
|
|
4
|
+
import { PuppeteerAdapter } from "./PuppeteerAdapter";
|
|
5
|
+
import { ChromiumManager } from "../browser/chromium";
|
|
6
|
+
import { loadConfig } from "../config";
|
|
7
|
+
|
|
8
|
+
// Marker lives ONLY in the XHR response body, never in the page HTML source,
|
|
9
|
+
// so html.includes(MARKER) is true iff the delayed XHR completed and injected.
|
|
10
|
+
const MARKER = "XHR_INJECTED_MARKER_7Q";
|
|
11
|
+
|
|
12
|
+
let server: Server;
|
|
13
|
+
let baseUrl: string;
|
|
14
|
+
let browser: Browser;
|
|
15
|
+
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
// Fixture page: 1500ms after load, an XHR resolves and injects MARKER.
|
|
18
|
+
// The delay is deliberately long so that, WITHOUT a network-idle wait, the
|
|
19
|
+
// HTML read definitively beats the XHR; WITH it, the read waits.
|
|
20
|
+
server = createServer((req, res) => {
|
|
21
|
+
if (req.url === "/late.json") {
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
24
|
+
res.end(JSON.stringify({ marker: MARKER }));
|
|
25
|
+
}, 1500);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
res.writeHead(200, { "content-type": "text/html" });
|
|
29
|
+
res.end(`<!doctype html><html><body><div id="late"></div>
|
|
30
|
+
<script>
|
|
31
|
+
fetch('/late.json').then(r => r.json()).then((d) => {
|
|
32
|
+
document.getElementById('late').textContent = d.marker;
|
|
33
|
+
});
|
|
34
|
+
</script></body></html>`);
|
|
35
|
+
});
|
|
36
|
+
await new Promise<void>(r => server.listen(0, r));
|
|
37
|
+
const addr = server.address();
|
|
38
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
39
|
+
baseUrl = `http://127.0.0.1:${port}`;
|
|
40
|
+
|
|
41
|
+
const config = loadConfig();
|
|
42
|
+
browser = await new ChromiumManager(config).launch();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterAll(async () => {
|
|
46
|
+
await browser?.close();
|
|
47
|
+
await new Promise<void>(r => server.close(() => r()));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Prefixed "scanner service:" so the CI `test:unit` job (which has no browser)
|
|
51
|
+
// excludes it via the `^(?!scanner service)` name filter. Runs under `bun test`.
|
|
52
|
+
describe("scanner service: PuppeteerAdapter.waitForNetworkIdle", () => {
|
|
53
|
+
it("waits for the late XHR so the injected content is present", async () => {
|
|
54
|
+
const page = await browser.newPage();
|
|
55
|
+
const adapter = new PuppeteerAdapter(page);
|
|
56
|
+
await adapter.goto(`${baseUrl}/`, { waitUntil: "load" });
|
|
57
|
+
await adapter.waitForNetworkIdle?.();
|
|
58
|
+
const html = await adapter.content();
|
|
59
|
+
expect(html).toContain(MARKER);
|
|
60
|
+
await page.close();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Page } from "puppeteer-core";
|
|
2
2
|
import type { BrowserAdapter } from "@sudobility/testomniac_runner_service";
|
|
3
|
+
import {
|
|
4
|
+
NetworkIdleTracker,
|
|
5
|
+
waitForNetworkIdle,
|
|
6
|
+
} from "@sudobility/testomniac_runner_service";
|
|
3
7
|
import pino from "pino";
|
|
4
8
|
|
|
5
9
|
const logger = pino({ name: "puppeteer-adapter" });
|
|
@@ -17,8 +21,37 @@ export class PuppeteerAdapter implements BrowserAdapter {
|
|
|
17
21
|
contentType: string;
|
|
18
22
|
}> = [];
|
|
19
23
|
|
|
24
|
+
private readonly idleTracker = new NetworkIdleTracker();
|
|
25
|
+
|
|
20
26
|
constructor(page: Page) {
|
|
21
27
|
this.page = page;
|
|
28
|
+
this.bindNetworkIdleTracking(page);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private bindNetworkIdleTracking(page: Page): void {
|
|
32
|
+
// Puppeteer request objects have no stable id across events, so key on
|
|
33
|
+
// URL + resourceType. Collisions only cause a marginally early `end`,
|
|
34
|
+
// which is harmless for idle detection.
|
|
35
|
+
page.on("request", req =>
|
|
36
|
+
this.idleTracker.start(
|
|
37
|
+
req.url() + "\0" + req.resourceType(),
|
|
38
|
+
req.resourceType()
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
const done = (req: { url: () => string; resourceType: () => string }) =>
|
|
42
|
+
this.idleTracker.end(req.url() + "\0" + req.resourceType());
|
|
43
|
+
page.on("requestfinished", done);
|
|
44
|
+
page.on("requestfailed", done);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async waitForNetworkIdle(opts?: {
|
|
48
|
+
idleMs?: number;
|
|
49
|
+
floorMs?: number;
|
|
50
|
+
staleMs?: number;
|
|
51
|
+
timeout?: number;
|
|
52
|
+
pollMs?: number;
|
|
53
|
+
}): Promise<void> {
|
|
54
|
+
await waitForNetworkIdle(this.idleTracker, opts);
|
|
22
55
|
}
|
|
23
56
|
|
|
24
57
|
private async materializeSelector(selector: string): Promise<string> {
|
|
@@ -152,7 +185,7 @@ export class PuppeteerAdapter implements BrowserAdapter {
|
|
|
152
185
|
options?: { waitUntil?: string; timeout?: number }
|
|
153
186
|
): Promise<void> {
|
|
154
187
|
await this.page.goto(url, {
|
|
155
|
-
waitUntil: (options?.waitUntil as any) || "
|
|
188
|
+
waitUntil: (options?.waitUntil as any) || "load",
|
|
156
189
|
timeout: options?.timeout || 30000,
|
|
157
190
|
});
|
|
158
191
|
this.currentUrl = this.page.url();
|
|
@@ -210,7 +243,7 @@ export class PuppeteerAdapter implements BrowserAdapter {
|
|
|
210
243
|
}): Promise<void> {
|
|
211
244
|
try {
|
|
212
245
|
await this.page.waitForNavigation({
|
|
213
|
-
waitUntil: (options?.waitUntil as any) || "
|
|
246
|
+
waitUntil: (options?.waitUntil as any) || "load",
|
|
214
247
|
timeout: options?.timeout || 5000,
|
|
215
248
|
});
|
|
216
249
|
} catch (err) {
|