Compare commits
2 Commits
main
...
fix/evalua
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52edef225b | ||
|
|
609aa4a7c5 |
@ -18,6 +18,7 @@
|
|||||||
- Discord: emit system events for reaction add/remove with per-guild reaction notifications (off|own|all|allowlist) (#140) — thanks @thewilloftheshadow.
|
- Discord: emit system events for reaction add/remove with per-guild reaction notifications (off|own|all|allowlist) (#140) — thanks @thewilloftheshadow.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
- Browser tools: tolerate trailing semicolons in evaluate expressions (#153) — thanks @azade-c.
|
||||||
- Auto-reply: drop final payloads when block streaming to avoid duplicate Discord sends.
|
- Auto-reply: drop final payloads when block streaming to avoid duplicate Discord sends.
|
||||||
- Bash tool: default auto-background delay to 10s.
|
- Bash tool: default auto-background delay to 10s.
|
||||||
- Telegram: chunk block-stream replies to avoid “message is too long” errors (#124) — thanks @mukhtharcm.
|
- Telegram: chunk block-stream replies to avoid “message is too long” errors (#124) — thanks @mukhtharcm.
|
||||||
|
|||||||
@ -232,4 +232,41 @@ describe("pw-tools-core", () => {
|
|||||||
expect(dismiss).toHaveBeenCalled();
|
expect(dismiss).toHaveBeenCalled();
|
||||||
expect(accept).not.toHaveBeenCalled();
|
expect(accept).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("evaluates expressions with trailing semicolons", async () => {
|
||||||
|
const evaluate = vi.fn(
|
||||||
|
async (fn: (body: string) => unknown, body: string) => fn(body),
|
||||||
|
);
|
||||||
|
currentPage = { evaluate };
|
||||||
|
|
||||||
|
const mod = await importModule();
|
||||||
|
const result = await mod.evaluateViaPlaywright({
|
||||||
|
cdpUrl: "http://127.0.0.1:18792",
|
||||||
|
fn: "() => 42;",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(42);
|
||||||
|
expect(evaluate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("evaluates element expressions with trailing semicolons", async () => {
|
||||||
|
const evaluate = vi.fn(
|
||||||
|
async (
|
||||||
|
fn: (el: { tagName: string }, body: string) => unknown,
|
||||||
|
body: string,
|
||||||
|
) => fn({ tagName: "DIV" }, body),
|
||||||
|
);
|
||||||
|
currentRefLocator = { evaluate };
|
||||||
|
currentPage = {};
|
||||||
|
|
||||||
|
const mod = await importModule();
|
||||||
|
const result = await mod.evaluateViaPlaywright({
|
||||||
|
cdpUrl: "http://127.0.0.1:18792",
|
||||||
|
fn: "(el) => el.tagName;",
|
||||||
|
ref: "1",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe("DIV");
|
||||||
|
expect(sessionMocks.refLocator).toHaveBeenCalledWith(currentPage, "1");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -207,53 +207,44 @@ export async function evaluateViaPlaywright(opts: {
|
|||||||
}): Promise<unknown> {
|
}): Promise<unknown> {
|
||||||
const fnText = String(opts.fn ?? "").trim();
|
const fnText = String(opts.fn ?? "").trim();
|
||||||
if (!fnText) throw new Error("function is required");
|
if (!fnText) throw new Error("function is required");
|
||||||
|
const fnBody = fnText.replace(/[;\s]+$/g, "");
|
||||||
|
if (!fnBody) throw new Error("function is required");
|
||||||
const page = await getPageForTargetId(opts);
|
const page = await getPageForTargetId(opts);
|
||||||
ensurePageState(page);
|
ensurePageState(page);
|
||||||
if (opts.ref) {
|
if (opts.ref) {
|
||||||
const locator = refLocator(page, opts.ref);
|
const locator = refLocator(page, opts.ref);
|
||||||
return await locator.evaluate((el, fnBody) => {
|
// Use Function constructor at runtime to avoid esbuild adding __name helper
|
||||||
const compileRunner = (body: string) => {
|
// which doesn't exist in the browser context
|
||||||
const inner = `"use strict"; const candidate = ${body}; return typeof candidate === "function" ? candidate(element) : candidate;`;
|
const elementEvaluator = new Function(
|
||||||
// This intentionally evaluates user-supplied code in the browser context.
|
"el",
|
||||||
// oxlint-disable-next-line typescript-eslint/no-implied-eval
|
"fnBody",
|
||||||
return new Function("element", inner) as (element: Element) => unknown;
|
`
|
||||||
};
|
"use strict";
|
||||||
let compiled: unknown;
|
|
||||||
try {
|
try {
|
||||||
compiled = compileRunner(fnBody);
|
var candidate = eval("(" + fnBody + ")");
|
||||||
|
return typeof candidate === "function" ? candidate(el) : candidate;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message =
|
throw new Error("Invalid evaluate function: " + (err && err.message ? err.message : String(err)));
|
||||||
err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: typeof err === "string"
|
|
||||||
? err
|
|
||||||
: "invalid expression";
|
|
||||||
throw new Error(`Invalid evaluate function: ${message}`);
|
|
||||||
}
|
}
|
||||||
return (compiled as (element: Element) => unknown)(el as Element);
|
`,
|
||||||
}, fnText);
|
) as (el: Element, fnBody: string) => unknown;
|
||||||
|
return await locator.evaluate(elementEvaluator, fnBody);
|
||||||
}
|
}
|
||||||
return await page.evaluate((fnBody) => {
|
// Use Function constructor at runtime to avoid esbuild adding __name helper
|
||||||
const compileRunner = (body: string) => {
|
// which doesn't exist in the browser context
|
||||||
const inner = `"use strict"; const candidate = ${body}; return typeof candidate === "function" ? candidate() : candidate;`;
|
const browserEvaluator = new Function(
|
||||||
// This intentionally evaluates user-supplied code in the browser context.
|
"fnBody",
|
||||||
// oxlint-disable-next-line typescript-eslint/no-implied-eval
|
`
|
||||||
return new Function(inner) as () => unknown;
|
"use strict";
|
||||||
};
|
|
||||||
let compiled: unknown;
|
|
||||||
try {
|
try {
|
||||||
compiled = compileRunner(fnBody);
|
var candidate = eval("(" + fnBody + ")");
|
||||||
|
return typeof candidate === "function" ? candidate() : candidate;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message =
|
throw new Error("Invalid evaluate function: " + (err && err.message ? err.message : String(err)));
|
||||||
err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: typeof err === "string"
|
|
||||||
? err
|
|
||||||
: "invalid expression";
|
|
||||||
throw new Error(`Invalid evaluate function: ${message}`);
|
|
||||||
}
|
}
|
||||||
return (compiled as () => unknown)();
|
`,
|
||||||
}, fnText);
|
) as (fnBody: string) => unknown;
|
||||||
|
return await page.evaluate(browserEvaluator, fnBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function armFileUploadViaPlaywright(opts: {
|
export async function armFileUploadViaPlaywright(opts: {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user