152 lines
5.9 KiB
JavaScript
152 lines
5.9 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const { chromium } = require('playwright');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const PROFILE = path.join(ROOT, 'diagnostics', 'erp-playwright-profile');
|
|
const RUN_ID = new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
|
|
const RUN_DIR = path.join(ROOT, 'diagnostics', 'split-parent-execute', `run-${RUN_ID}`);
|
|
|
|
const ORDER = {
|
|
productId: '412',
|
|
productName: '老挝行程--广东 8D7N',
|
|
dateFrom: '2026-07-01',
|
|
dateTo: '2026-07-31',
|
|
explicitDates: ['2026-7-4', '2026-7-11', '2026-7-18', '2026-7-25'],
|
|
plannedGuests: 10,
|
|
op: '测试',
|
|
};
|
|
|
|
fs.mkdirSync(RUN_DIR, { recursive: true });
|
|
|
|
function log(message, data) {
|
|
console.log(`[${new Date().toISOString()}] ${message}${data === undefined ? '' : ` ${JSON.stringify(data)}`}`);
|
|
}
|
|
|
|
async function setField(frame, selector, value) {
|
|
const locator = frame.locator(selector).first();
|
|
await locator.fill(String(value));
|
|
await locator.evaluate((el) => {
|
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
el.dispatchEvent(new Event('blur', { bubbles: true }));
|
|
});
|
|
}
|
|
|
|
async function getPlanFrame(page) {
|
|
for (let i = 0; i < 60; i += 1) {
|
|
const frame = page.frames().find((f) => f.url().includes('/Business/plan_add.asp'));
|
|
if (frame) return frame;
|
|
await page.waitForTimeout(500);
|
|
}
|
|
throw new Error('plan_add frame not found');
|
|
}
|
|
|
|
async function readAllText(page) {
|
|
const texts = [];
|
|
for (const frame of page.frames()) {
|
|
const text = await frame.evaluate(() => document.body.innerText.replace(/\s+/g, ' ').trim()).catch(() => '');
|
|
if (text) texts.push(text);
|
|
}
|
|
return texts.join('\n');
|
|
}
|
|
|
|
function extractGroupNumbers(text) {
|
|
return Array.from(new Set(text.match(/[A-Z]{1,4}-\d{6,8}[A-Z]?(?:-[A-Z])?/g) || []));
|
|
}
|
|
|
|
async function selectExplicitDates(frame, dates) {
|
|
await frame.evaluate((targetDates) => {
|
|
const seen = new Set();
|
|
for (const el of Array.from(document.querySelectorAll('input[name="zhidingzhouqi"]'))) {
|
|
el.checked = false;
|
|
if (targetDates.includes(el.value) && !seen.has(el.value)) {
|
|
el.checked = true;
|
|
seen.add(el.value);
|
|
}
|
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
}, dates);
|
|
const selected = await frame.evaluate(() =>
|
|
Array.from(document.querySelectorAll('input[name="zhidingzhouqi"]'))
|
|
.filter((el) => el.checked)
|
|
.map((el) => el.value)
|
|
);
|
|
const missing = dates.filter((d) => !selected.includes(d));
|
|
if (missing.length) throw new Error(`explicit date checkboxes missing after selection: ${missing.join(', ')}`);
|
|
if (selected.length !== dates.length) throw new Error(`selected date count mismatch: ${selected.join(', ')}`);
|
|
log('selected-explicit-dates', selected);
|
|
}
|
|
|
|
async function clickDialogOk(page) {
|
|
const selectors = ['button:has-text("确定")', 'input[value="确定"]', 'a:has-text("确定")', '.aui_state_highlight'];
|
|
for (const selector of selectors) {
|
|
const locator = page.locator(selector).first();
|
|
if (await locator.count().catch(() => 0)) {
|
|
await locator.click().catch(() => {});
|
|
await page.waitForTimeout(500);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async function main() {
|
|
log('run-start', { RUN_DIR, ORDER });
|
|
const context = await chromium.launchPersistentContext(PROFILE, {
|
|
headless: false,
|
|
viewport: { width: 1920, height: 1000 },
|
|
acceptDownloads: true,
|
|
});
|
|
const page = context.pages()[0] || await context.newPage();
|
|
page.setDefaultTimeout(15000);
|
|
|
|
try {
|
|
await page.goto('https://ltjt.yunzhi.run/System/Business/plan.asp', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
await page.waitForTimeout(1200);
|
|
await page.locator('input[value="新增计划"], a:has-text("新增计划"), button:has-text("新增计划")').first().click();
|
|
const frame = await getPlanFrame(page);
|
|
await frame.waitForSelector('#Riqi1', { timeout: 30000 });
|
|
await setField(frame, '#Riqi1', ORDER.dateFrom);
|
|
await setField(frame, '#Riqi2', ORDER.dateTo);
|
|
await setField(frame, '#jihuashu', ORDER.plannedGuests);
|
|
await setField(frame, '#jiedairen', ORDER.op);
|
|
await page.waitForTimeout(1200);
|
|
await selectExplicitDates(frame, ORDER.explicitDates);
|
|
await frame.locator(`input[name="cpid"][value="${ORDER.productId}"]`).check();
|
|
await page.waitForTimeout(1200);
|
|
await page.screenshot({ path: path.join(RUN_DIR, '01-before-save.png'), fullPage: false });
|
|
|
|
const before = await readAllText(page);
|
|
fs.writeFileSync(path.join(RUN_DIR, 'before-save.txt'), before, 'utf8');
|
|
if (!before.includes(ORDER.productName.replace(' 8D7N', ''))) {
|
|
throw new Error(`target product not visible before save: ${ORDER.productName}`);
|
|
}
|
|
|
|
await frame.locator('#SubmitButton, [name="SubmitButton"]').first().click();
|
|
await page.waitForTimeout(4000);
|
|
await page.screenshot({ path: path.join(RUN_DIR, '02-after-save.png'), fullPage: false });
|
|
const after = await readAllText(page);
|
|
fs.writeFileSync(path.join(RUN_DIR, 'after-save.txt'), after, 'utf8');
|
|
const groupNumbers = extractGroupNumbers(after).filter((n) => n.startsWith('LW-2607'));
|
|
if (groupNumbers.length < ORDER.explicitDates.length) {
|
|
throw new Error(`could not capture enough parent group numbers: ${groupNumbers.join(', ')}`);
|
|
}
|
|
await clickDialogOk(page);
|
|
const result = { runDir: RUN_DIR, dates: ORDER.explicitDates, parentGroupNumbers: groupNumbers, text: after.slice(0, 1200) };
|
|
fs.writeFileSync(path.join(RUN_DIR, 'result.json'), JSON.stringify(result, null, 2), 'utf8');
|
|
log('run-complete', result);
|
|
} catch (error) {
|
|
log('error', { message: error.message, stack: error.stack });
|
|
await page.screenshot({ path: path.join(RUN_DIR, 'error.png'), fullPage: false }).catch(() => {});
|
|
throw error;
|
|
} finally {
|
|
await context.close();
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|