PMFlow/frontend/e2e/feishu-project-full-flow.he...

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";
}