96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
import type { ElementHandle, Page } from "@playwright/test";
|
|
|
|
export type DraftConfirmSubmissionMode = "card_button_then_text_message" | "text_message";
|
|
|
|
async function pickBottomComposerOrNull(page: Page): Promise<ElementHandle<HTMLElement> | null> {
|
|
const candidates = await page.locator('textarea, [contenteditable="true"]').elementHandles();
|
|
let bestHandle: ElementHandle<HTMLElement> | null = null;
|
|
let bestBottom = -1;
|
|
for (const candidate of candidates) {
|
|
const asHtml = candidate as ElementHandle<HTMLElement>;
|
|
const box = await asHtml.boundingBox();
|
|
if (!box || box.width < 60 || box.height < 16) {
|
|
continue;
|
|
}
|
|
const visible = await asHtml.isVisible();
|
|
if (!visible) {
|
|
continue;
|
|
}
|
|
const bottom = box.y + box.height;
|
|
if (bottom > bestBottom) {
|
|
bestBottom = bottom;
|
|
bestHandle = asHtml;
|
|
}
|
|
}
|
|
return bestHandle;
|
|
}
|
|
|
|
async function pickBottomComposer(page: Page): Promise<ElementHandle<HTMLElement>> {
|
|
const bestHandle = await pickBottomComposerOrNull(page);
|
|
if (!bestHandle) {
|
|
throw new Error("No visible Feishu message composer was found.");
|
|
}
|
|
return bestHandle;
|
|
}
|
|
|
|
export async function sendChatMessage(page: Page, message: string, sendShortcut: string): Promise<void> {
|
|
const composer = await pickBottomComposer(page);
|
|
const isTextArea = await composer.evaluate((node) => node.tagName.toLowerCase() === "textarea");
|
|
await composer.click({ force: true });
|
|
const selectAllShortcut = process.platform === "darwin" ? "Meta+A" : "Control+A";
|
|
await page.keyboard.press(selectAllShortcut).catch(() => undefined);
|
|
await page.keyboard.press("Backspace").catch(() => undefined);
|
|
if (isTextArea) {
|
|
await composer.fill(message);
|
|
} else {
|
|
await page.keyboard.type(message, { delay: 18 });
|
|
}
|
|
await page.keyboard.press(sendShortcut);
|
|
}
|
|
|
|
async function pickBottomVisibleConfirmButton(page: Page): Promise<ElementHandle<HTMLElement> | null> {
|
|
const candidates = await page.getByRole("button", { name: "确认", exact: true }).elementHandles();
|
|
let bestHandle: ElementHandle<HTMLElement> | null = null;
|
|
let bestBottom = -1;
|
|
for (const candidate of candidates) {
|
|
const asHtml = candidate as ElementHandle<HTMLElement>;
|
|
const box = await asHtml.boundingBox();
|
|
if (!box || box.width < 32 || box.height < 20) {
|
|
continue;
|
|
}
|
|
const visible = await asHtml.isVisible();
|
|
if (!visible) {
|
|
continue;
|
|
}
|
|
const bottom = box.y + box.height;
|
|
if (bottom > bestBottom) {
|
|
bestBottom = bottom;
|
|
bestHandle = asHtml;
|
|
}
|
|
}
|
|
return bestHandle;
|
|
}
|
|
|
|
export async function confirmDraftSubmission(
|
|
page: Page,
|
|
{
|
|
sendShortcut,
|
|
workItemId,
|
|
}: {
|
|
sendShortcut: string;
|
|
workItemId: number;
|
|
},
|
|
): Promise<DraftConfirmSubmissionMode> {
|
|
const confirmMessage = `work_item #${workItemId} 确认`;
|
|
const confirmButton = await pickBottomVisibleConfirmButton(page);
|
|
if (confirmButton) {
|
|
await confirmButton.click({ force: true });
|
|
await page.waitForTimeout(1_200);
|
|
await sendChatMessage(page, confirmMessage, sendShortcut);
|
|
return "card_button_then_text_message";
|
|
}
|
|
|
|
await sendChatMessage(page, confirmMessage, sendShortcut);
|
|
return "text_message";
|
|
}
|