@epoint-testtech/ep-stage-skill 0.0.11 → 0.0.13
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/codex-skill/ep-stage/code-list-generate/SKILL.md +168 -0
- package/codex-skill/ep-stage/code-list-generate/assets/code_list.template.json5 +325 -0
- package/codex-skill/ep-stage/code-list-generate/assets/example-zwplace.json5 +588 -0
- package/codex-skill/ep-stage/code-list-generate/evals/evals.json +47 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/action/SchemeResourceAddAction.java +119 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/action/SchemeResourceDetailAction.java +70 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/action/SchemeResourceEditAction.java +107 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/action/SchemeResourceListAction.java +167 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/api/ISchemeResourceService.java +84 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/api/entity/SchemeResource.java +218 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/impl/SchemeResourceService.java +146 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/java/com/epoint/schemeresource/impl/SchemeResourceServiceImpl.java +110 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/webapp/perpage/schemeresource/schemeresourceadd.html +158 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/webapp/perpage/schemeresource/schemeresourcedetail.html +89 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/webapp/perpage/schemeresource/schemeresourceedit.html +125 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/schemeresource-req/src/main/webapp/perpage/schemeresource/schemeresourcelist.html +224 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/docs//345/234/272/346/211/200/347/252/227/345/217/243/344/277/241/346/201/257/347/256/241/347/220/206/345/217/212/345/220/204/347/253/257/347/273/237/350/256/241/346/243/200/347/264/242/346/237/245/347/234/213_prd.md +602 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/action/GxhZwPlaceAddAction.java +210 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/action/GxhZwPlaceDetailAction.java +69 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/action/GxhZwPlaceEditAction.java +235 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java +145 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/api/IGxhZwPlaceService.java +87 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/api/entity/GxhZwPlace.java +306 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/api/entity/GxhZwPlaceHistory.java +205 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/api/entity/GxhZwWindow.java +184 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/impl/GxhZwPlaceService.java +151 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/java/com/epoint/zwplace/impl/GxhZwPlaceServiceImpl.java +96 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/webapp/perpage/zwplace/gxhzwplaceadd.html +253 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/webapp/perpage/zwplace/gxhzwplacedetail.html +146 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/webapp/perpage/zwplace/gxhzwplaceedit.html +275 -0
- package/codex-skill/ep-stage/code-list-generate/evals/fixtures/zwplace-req/src/main/webapp/perpage/zwplace/gxhzwplacelist.html +168 -0
- package/codex-skill/ep-stage/code-list-generate/evals/grade-code-list.mjs +184 -0
- package/codex-skill/ep-stage/code-list-generate/references/epoint-extraction.md +106 -0
- package/codex-skill/ep-stage/code-list-generate/references/field-contract.md +122 -0
- package/codex-skill/ep-stage/code-list-generate/scripts/validate-code-list.mjs +202 -0
- package/codex-skill/ep-stage/glue-create-project/SKILL.md +227 -223
- package/dist/src/extractors/code-list-json5.d.ts +5 -0
- package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.d.ts +4 -0
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +399 -362
- package/dist/src/testcase/contract-testcase-assembler.d.ts +10 -1
- package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
- package/dist/src/testcase/contract-testcase-assembler.js +46 -13
- package/package.json +1 -1
|
@@ -385,18 +385,18 @@ function renderSearchCondition(condition, level) {
|
|
|
385
385
|
if (condition.searchAssertion !== undefined) {
|
|
386
386
|
lines.push(`${innerIndent}searchAssertion: ${singleQuoted(condition.searchAssertion)},`);
|
|
387
387
|
}
|
|
388
|
-
return `${indent}{
|
|
389
|
-
${lines.join('\n')}
|
|
388
|
+
return `${indent}{
|
|
389
|
+
${lines.join('\n')}
|
|
390
390
|
${renderListboxSelections(condition.createSelections, level + 4)}${indent}}`;
|
|
391
391
|
}
|
|
392
|
-
return `${indent}{
|
|
393
|
-
${innerIndent}component: 'input',
|
|
394
|
-
${innerIndent}field: ${singleQuoted(condition.field)},
|
|
395
|
-
${innerIndent}label: ${singleQuoted(condition.label)},
|
|
396
|
-
${innerIndent}seedValue: ${singleQuoted(condition.seedValue)},
|
|
397
|
-
${renderOptionalStringProperty('strategy', condition.strategy, level + 4)}${renderOptionalStringProperty('fixedValue', condition.fixedValue, level + 4)}${renderOptionalStringProperty('updatePrefix', condition.updatePrefix, level + 4)}${renderOptionalStringProperty('searchSeedValue', condition.searchSeedValue, level + 4)}${innerIndent}addLocator: ${locatorLiteral(condition.addLocator)},
|
|
398
|
-
${innerIndent}editLocator: ${locatorLiteral(condition.editLocator)},
|
|
399
|
-
${innerIndent}listLocator: ${locatorLiteral(condition.listLocator)},
|
|
392
|
+
return `${indent}{
|
|
393
|
+
${innerIndent}component: 'input',
|
|
394
|
+
${innerIndent}field: ${singleQuoted(condition.field)},
|
|
395
|
+
${innerIndent}label: ${singleQuoted(condition.label)},
|
|
396
|
+
${innerIndent}seedValue: ${singleQuoted(condition.seedValue)},
|
|
397
|
+
${renderOptionalStringProperty('strategy', condition.strategy, level + 4)}${renderOptionalStringProperty('fixedValue', condition.fixedValue, level + 4)}${renderOptionalStringProperty('updatePrefix', condition.updatePrefix, level + 4)}${renderOptionalStringProperty('searchSeedValue', condition.searchSeedValue, level + 4)}${innerIndent}addLocator: ${locatorLiteral(condition.addLocator)},
|
|
398
|
+
${innerIndent}editLocator: ${locatorLiteral(condition.editLocator)},
|
|
399
|
+
${innerIndent}listLocator: ${locatorLiteral(condition.listLocator)},
|
|
400
400
|
${indent}}`;
|
|
401
401
|
}
|
|
402
402
|
function renderSearchConditions(conditions, level) {
|
|
@@ -411,28 +411,30 @@ function renderSearchConditions(conditions, level) {
|
|
|
411
411
|
* @returns TypeScript 对象源码
|
|
412
412
|
*/
|
|
413
413
|
export function renderStageSkeletonCrudSlots(slots) {
|
|
414
|
-
return `{
|
|
415
|
-
moduleId: ${singleQuoted(slots.moduleId)},
|
|
416
|
-
${renderSearchConditions(slots.searchConditions, 8)}
|
|
417
|
-
frames: {
|
|
418
|
-
listIframeSrcKeyword: ${singleQuoted(slots.frames.listIframeSrcKeyword)},
|
|
419
|
-
addIframeSrcKeyword: ${singleQuoted(slots.frames.addIframeSrcKeyword)},
|
|
420
|
-
editIframeSrcKeyword: ${singleQuoted(slots.frames.editIframeSrcKeyword)},
|
|
421
|
-
},
|
|
422
|
-
locators: {
|
|
423
|
-
createButton: ${locatorLiteral(slots.locators.createButton)},
|
|
424
|
-
${renderOptionalStringProperty('addAutofillTrigger', slots.locators.addAutofillTrigger, 12)}
|
|
425
|
-
addSaveButton: ${locatorLiteral(slots.locators.addSaveButton)},
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
${
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
414
|
+
return `{
|
|
415
|
+
moduleId: ${singleQuoted(slots.moduleId)},
|
|
416
|
+
${renderSearchConditions(slots.searchConditions, 8)}
|
|
417
|
+
frames: {
|
|
418
|
+
listIframeSrcKeyword: ${singleQuoted(slots.frames.listIframeSrcKeyword)},
|
|
419
|
+
addIframeSrcKeyword: ${singleQuoted(slots.frames.addIframeSrcKeyword)},
|
|
420
|
+
editIframeSrcKeyword: ${singleQuoted(slots.frames.editIframeSrcKeyword)},
|
|
421
|
+
},
|
|
422
|
+
locators: {
|
|
423
|
+
createButton: ${locatorLiteral(slots.locators.createButton)},
|
|
424
|
+
${renderOptionalStringProperty('addAutofillTrigger', slots.locators.addAutofillTrigger, 12)}
|
|
425
|
+
addSaveButton: ${locatorLiteral(slots.locators.addSaveButton)},
|
|
426
|
+
${renderOptionalStringProperty('addDraftSaveButton', slots.locators.addDraftSaveButton, 12)}
|
|
427
|
+
${renderOptionalStringProperty('addDialogCloseButton', slots.locators.addDialogCloseButton, 12)}
|
|
428
|
+
listSearchSubmit: ${locatorLiteral(slots.locators.listSearchSubmit)},
|
|
429
|
+
listResultValueLocatorTemplate: ${locatorLiteral(slots.locators.listResultValueLocatorTemplate)},
|
|
430
|
+
listEditActionLocatorTemplate: ${locatorLiteral(slots.locators.listEditActionLocatorTemplate)},
|
|
431
|
+
editSaveButton: ${locatorLiteral(slots.locators.editSaveButton)},
|
|
432
|
+
listRowSelectLocatorTemplate: ${locatorLiteral(slots.locators.listRowSelectLocatorTemplate)},
|
|
433
|
+
listDeleteButton: ${locatorLiteral(slots.locators.listDeleteButton)},
|
|
434
|
+
${renderOptionalStringProperty('deleteConfirmButton', slots.locators.deleteConfirmButton, 12)} },
|
|
435
|
+
assertions: {
|
|
436
|
+
deletePolicy: ${singleQuoted(slots.assertions.deletePolicy)},
|
|
437
|
+
${renderOptionalStringProperty('blockedDeleteMessage', slots.assertions.blockedDeleteMessage, 12)} },
|
|
436
438
|
}`;
|
|
437
439
|
}
|
|
438
440
|
function renderChildGridColumns(columns, level) {
|
|
@@ -479,13 +481,13 @@ function renderChildGridLocators(locators, level) {
|
|
|
479
481
|
export function renderChildGridSlots(slots) {
|
|
480
482
|
if (slots.length === 0)
|
|
481
483
|
return '[]';
|
|
482
|
-
const items = slots.map((slot) => `{
|
|
483
|
-
gridId: ${singleQuoted(slot.gridId)},
|
|
484
|
-
label: ${singleQuoted(slot.label)},
|
|
485
|
-
pageRole: ${singleQuoted(slot.pageRole)},
|
|
486
|
-
editMode: ${singleQuoted(slot.editMode)},
|
|
487
|
-
columns: ${renderChildGridColumns(slot.columns, 8).trimStart()},
|
|
488
|
-
locators: ${renderChildGridLocators(slot.locators, 8).trimStart()},
|
|
484
|
+
const items = slots.map((slot) => `{
|
|
485
|
+
gridId: ${singleQuoted(slot.gridId)},
|
|
486
|
+
label: ${singleQuoted(slot.label)},
|
|
487
|
+
pageRole: ${singleQuoted(slot.pageRole)},
|
|
488
|
+
editMode: ${singleQuoted(slot.editMode)},
|
|
489
|
+
columns: ${renderChildGridColumns(slot.columns, 8).trimStart()},
|
|
490
|
+
locators: ${renderChildGridLocators(slot.locators, 8).trimStart()},
|
|
489
491
|
}`).join(',\n');
|
|
490
492
|
return `[\n${items}\n]`;
|
|
491
493
|
}
|
|
@@ -501,10 +503,10 @@ function renderChildGridFillSteps(slot, valuePrefix, indent = 10) {
|
|
|
501
503
|
const spaces = ' '.repeat(indent);
|
|
502
504
|
return slot.columns.map((col) => {
|
|
503
505
|
if (col.component === 'input') {
|
|
504
|
-
return `${spaces}await childGridPage.editCell(1, ${singleQuoted(col.field)});
|
|
506
|
+
return `${spaces}await childGridPage.editCell(1, ${singleQuoted(col.field)});
|
|
505
507
|
${spaces}await childGridPage.fillInputCell(${singleQuoted(`${valuePrefix}${col.label}`)});`;
|
|
506
508
|
}
|
|
507
|
-
return `${spaces}await childGridPage.editCell(1, ${singleQuoted(col.field)});
|
|
509
|
+
return `${spaces}await childGridPage.editCell(1, ${singleQuoted(col.field)});
|
|
508
510
|
${spaces}await childGridPage.selectListboxCell(childGridSlots[0].columns.find((c) => c.field === ${singleQuoted(col.field)})!);`;
|
|
509
511
|
}).join('\n');
|
|
510
512
|
}
|
|
@@ -532,38 +534,38 @@ function renderChildGridCreateTest(slot, crudSlots, moduleLabel, moduleId, caseR
|
|
|
532
534
|
const label = slot.label;
|
|
533
535
|
const fillSteps = renderChildGridFillSteps(slot, '测试');
|
|
534
536
|
const verifyAssertions = renderChildGridVerifyAssertions(slot, '测试');
|
|
535
|
-
return ` test(${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-新增行并随主表保存`)}, async ({ page }) => {
|
|
536
|
-
test.slow();
|
|
537
|
-
|
|
538
|
-
await runRecordedWorkflow(
|
|
539
|
-
recorder,
|
|
540
|
-
workflowMeta({
|
|
541
|
-
workflowId: ${singleQuoted(`${moduleId}.childGrid.${slot.gridId}.create`)},
|
|
542
|
-
workflowKind: 'create',
|
|
543
|
-
workflowName: ${singleQuoted(caseRef?.title ?? `${label}-新增行`)},
|
|
544
|
-
businessIntent: ${singleQuoted(`在新增${moduleLabel}时维护${label}子表,验证子表数据随主表保存并持久化。`)},
|
|
545
|
-
elementActions: ['进入新增页', '点击子表新增行', '填写子表单元格', '保存主表', '返回列表搜索并校验子表数据'],
|
|
546
|
-
assertionExpectation: ${singleQuoted(`保存后重新打开编辑页,${label}子表应显示新增的行数据。`)},
|
|
547
|
-
playwrightTestTitle: ${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-新增行并随主表保存`)},
|
|
548
|
-
}),
|
|
549
|
-
async () => {
|
|
550
|
-
await openModule(page);
|
|
551
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
552
|
-
const childWorkflowData = crudPage.prepareWorkflowData();
|
|
553
|
-
await crudPage.runCreateWorkflowWithChildGrid(childWorkflowData, async (addContext) => {
|
|
554
|
-
const childGridPage = new ChildGridPage(page, addContext, childGridSlots[0]);
|
|
555
|
-
await childGridPage.addRow();
|
|
556
|
-
${fillSteps}
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
// 验证子表数据持久化:搜索主记录并重新打开编辑页
|
|
560
|
-
const editActionLocator = ${locatorLiteral(crudSlots.locators.listEditActionLocatorTemplate)}.replace('{{value}}', childWorkflowData.searchValue);
|
|
561
|
-
await page.locator(editActionLocator).click();
|
|
562
|
-
const editFrame = page.locator(\`iframe[src*="${crudSlots.frames.editIframeSrcKeyword}"]\`).contentFrame();
|
|
563
|
-
const verifyChildGridPage = new ChildGridPage(page, editFrame, childGridSlots[0]);
|
|
564
|
-
${verifyAssertions}
|
|
565
|
-
},
|
|
566
|
-
);
|
|
537
|
+
return ` test(${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-新增行并随主表保存`)}, async ({ page }) => {
|
|
538
|
+
test.slow();
|
|
539
|
+
|
|
540
|
+
await runRecordedWorkflow(
|
|
541
|
+
recorder,
|
|
542
|
+
workflowMeta({
|
|
543
|
+
workflowId: ${singleQuoted(`${moduleId}.childGrid.${slot.gridId}.create`)},
|
|
544
|
+
workflowKind: 'create',
|
|
545
|
+
workflowName: ${singleQuoted(caseRef?.title ?? `${label}-新增行`)},
|
|
546
|
+
businessIntent: ${singleQuoted(`在新增${moduleLabel}时维护${label}子表,验证子表数据随主表保存并持久化。`)},
|
|
547
|
+
elementActions: ['进入新增页', '点击子表新增行', '填写子表单元格', '保存主表', '返回列表搜索并校验子表数据'],
|
|
548
|
+
assertionExpectation: ${singleQuoted(`保存后重新打开编辑页,${label}子表应显示新增的行数据。`)},
|
|
549
|
+
playwrightTestTitle: ${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-新增行并随主表保存`)},
|
|
550
|
+
}),
|
|
551
|
+
async () => {
|
|
552
|
+
await openModule(page);
|
|
553
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
554
|
+
const childWorkflowData = crudPage.prepareWorkflowData();
|
|
555
|
+
await crudPage.runCreateWorkflowWithChildGrid(childWorkflowData, async (addContext) => {
|
|
556
|
+
const childGridPage = new ChildGridPage(page, addContext, childGridSlots[0]);
|
|
557
|
+
await childGridPage.addRow();
|
|
558
|
+
${fillSteps}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// 验证子表数据持久化:搜索主记录并重新打开编辑页
|
|
562
|
+
const editActionLocator = ${locatorLiteral(crudSlots.locators.listEditActionLocatorTemplate)}.replace('{{value}}', childWorkflowData.searchValue);
|
|
563
|
+
await page.locator(editActionLocator).click();
|
|
564
|
+
const editFrame = page.locator(\`iframe[src*="${crudSlots.frames.editIframeSrcKeyword}"]\`).contentFrame();
|
|
565
|
+
const verifyChildGridPage = new ChildGridPage(page, editFrame, childGridSlots[0]);
|
|
566
|
+
${verifyAssertions}
|
|
567
|
+
},
|
|
568
|
+
);
|
|
567
569
|
});`;
|
|
568
570
|
}
|
|
569
571
|
/**
|
|
@@ -573,50 +575,50 @@ function renderChildGridUpdateTest(slot, crudSlots, moduleLabel, moduleId, caseR
|
|
|
573
575
|
const label = slot.label;
|
|
574
576
|
const createFillSteps = renderChildGridFillSteps(slot, '测试');
|
|
575
577
|
const verifyAssertions = renderChildGridVerifyAssertions(slot, '修改后');
|
|
576
|
-
return ` test(${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-编辑行并随主表保存`)}, async ({ page }) => {
|
|
577
|
-
test.slow();
|
|
578
|
-
|
|
579
|
-
await runRecordedWorkflow(
|
|
580
|
-
recorder,
|
|
581
|
-
workflowMeta({
|
|
582
|
-
workflowId: ${singleQuoted(`${moduleId}.childGrid.${slot.gridId}.update`)},
|
|
583
|
-
workflowKind: 'update',
|
|
584
|
-
workflowName: ${singleQuoted(caseRef?.title ?? `${label}-编辑行`)},
|
|
585
|
-
businessIntent: ${singleQuoted(`在修改${moduleLabel}时维护${label}子表,验证子表数据随主表保存并持久化。`)},
|
|
586
|
-
elementActions: ['进入列表页', '创建带窗口信息子表的主记录', '打开编辑页', '修改子表单元格', '保存主表', '重新搜索并校验子表数据'],
|
|
587
|
-
assertionExpectation: ${singleQuoted(`保存后重新打开编辑页,${label}子表应显示更新后的行数据。`)},
|
|
588
|
-
playwrightTestTitle: ${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-编辑行并随主表保存`)},
|
|
589
|
-
}),
|
|
590
|
-
async () => {
|
|
591
|
-
await openModule(page);
|
|
592
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
593
|
-
const childWorkflowData = crudPage.prepareWorkflowData();
|
|
594
|
-
|
|
595
|
-
// 前置:创建带${label}子表的主记录
|
|
596
|
-
await crudPage.runCreateWorkflowWithChildGrid(childWorkflowData, async (addContext) => {
|
|
597
|
-
const childGridPage = new ChildGridPage(page, addContext, childGridSlots[0]);
|
|
598
|
-
await childGridPage.addRow();
|
|
599
|
-
${createFillSteps}
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
// 打开编辑页并修改子表行
|
|
603
|
-
const editActionLocator = ${locatorLiteral(crudSlots.locators.listEditActionLocatorTemplate)}.replace('{{value}}', childWorkflowData.searchValue);
|
|
604
|
-
await page.locator(editActionLocator).click();
|
|
605
|
-
const editFrame = page.locator(\`iframe[src*="${crudSlots.frames.editIframeSrcKeyword}"]\`).contentFrame();
|
|
606
|
-
const childGridPage = new ChildGridPage(page, editFrame, childGridSlots[0]);
|
|
607
|
-
${renderChildGridFillSteps(slot, '修改后', 8)}
|
|
608
|
-
|
|
609
|
-
// 保存主表
|
|
610
|
-
await editFrame.locator(crudSlots.locators.editSaveButton).click();
|
|
611
|
-
|
|
612
|
-
// 验证子表数据持久化:重新搜索主记录并打开编辑页
|
|
613
|
-
const verifyEditActionLocator = ${locatorLiteral(crudSlots.locators.listEditActionLocatorTemplate)}.replace('{{value}}', childWorkflowData.searchValue);
|
|
614
|
-
await page.locator(verifyEditActionLocator).click();
|
|
615
|
-
const verifyEditFrame = page.locator(\`iframe[src*="${crudSlots.frames.editIframeSrcKeyword}"]\`).contentFrame();
|
|
616
|
-
const verifyChildGridPage = new ChildGridPage(page, verifyEditFrame, childGridSlots[0]);
|
|
617
|
-
${verifyAssertions}
|
|
618
|
-
},
|
|
619
|
-
);
|
|
578
|
+
return ` test(${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-编辑行并随主表保存`)}, async ({ page }) => {
|
|
579
|
+
test.slow();
|
|
580
|
+
|
|
581
|
+
await runRecordedWorkflow(
|
|
582
|
+
recorder,
|
|
583
|
+
workflowMeta({
|
|
584
|
+
workflowId: ${singleQuoted(`${moduleId}.childGrid.${slot.gridId}.update`)},
|
|
585
|
+
workflowKind: 'update',
|
|
586
|
+
workflowName: ${singleQuoted(caseRef?.title ?? `${label}-编辑行`)},
|
|
587
|
+
businessIntent: ${singleQuoted(`在修改${moduleLabel}时维护${label}子表,验证子表数据随主表保存并持久化。`)},
|
|
588
|
+
elementActions: ['进入列表页', '创建带窗口信息子表的主记录', '打开编辑页', '修改子表单元格', '保存主表', '重新搜索并校验子表数据'],
|
|
589
|
+
assertionExpectation: ${singleQuoted(`保存后重新打开编辑页,${label}子表应显示更新后的行数据。`)},
|
|
590
|
+
playwrightTestTitle: ${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-编辑行并随主表保存`)},
|
|
591
|
+
}),
|
|
592
|
+
async () => {
|
|
593
|
+
await openModule(page);
|
|
594
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
595
|
+
const childWorkflowData = crudPage.prepareWorkflowData();
|
|
596
|
+
|
|
597
|
+
// 前置:创建带${label}子表的主记录
|
|
598
|
+
await crudPage.runCreateWorkflowWithChildGrid(childWorkflowData, async (addContext) => {
|
|
599
|
+
const childGridPage = new ChildGridPage(page, addContext, childGridSlots[0]);
|
|
600
|
+
await childGridPage.addRow();
|
|
601
|
+
${createFillSteps}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// 打开编辑页并修改子表行
|
|
605
|
+
const editActionLocator = ${locatorLiteral(crudSlots.locators.listEditActionLocatorTemplate)}.replace('{{value}}', childWorkflowData.searchValue);
|
|
606
|
+
await page.locator(editActionLocator).click();
|
|
607
|
+
const editFrame = page.locator(\`iframe[src*="${crudSlots.frames.editIframeSrcKeyword}"]\`).contentFrame();
|
|
608
|
+
const childGridPage = new ChildGridPage(page, editFrame, childGridSlots[0]);
|
|
609
|
+
${renderChildGridFillSteps(slot, '修改后', 8)}
|
|
610
|
+
|
|
611
|
+
// 保存主表
|
|
612
|
+
await editFrame.locator(crudSlots.locators.editSaveButton).click();
|
|
613
|
+
|
|
614
|
+
// 验证子表数据持久化:重新搜索主记录并打开编辑页
|
|
615
|
+
const verifyEditActionLocator = ${locatorLiteral(crudSlots.locators.listEditActionLocatorTemplate)}.replace('{{value}}', childWorkflowData.searchValue);
|
|
616
|
+
await page.locator(verifyEditActionLocator).click();
|
|
617
|
+
const verifyEditFrame = page.locator(\`iframe[src*="${crudSlots.frames.editIframeSrcKeyword}"]\`).contentFrame();
|
|
618
|
+
const verifyChildGridPage = new ChildGridPage(page, verifyEditFrame, childGridSlots[0]);
|
|
619
|
+
${verifyAssertions}
|
|
620
|
+
},
|
|
621
|
+
);
|
|
620
622
|
});`;
|
|
621
623
|
}
|
|
622
624
|
/**
|
|
@@ -628,49 +630,49 @@ ${verifyAssertions}
|
|
|
628
630
|
function renderChildGridDeleteTest(slot, crudSlots, moduleLabel, moduleId, caseRef) {
|
|
629
631
|
const label = slot.label;
|
|
630
632
|
const createFillSteps = renderChildGridFillSteps(slot, '测试');
|
|
631
|
-
return ` test(${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-删除行`)}, async ({ page }) => {
|
|
632
|
-
test.slow();
|
|
633
|
-
|
|
634
|
-
await runRecordedWorkflow(
|
|
635
|
-
recorder,
|
|
636
|
-
workflowMeta({
|
|
637
|
-
workflowId: ${singleQuoted(`${moduleId}.childGrid.${slot.gridId}.delete`)},
|
|
638
|
-
workflowKind: 'delete',
|
|
639
|
-
workflowName: ${singleQuoted(caseRef?.title ?? `${label}-删除行`)},
|
|
640
|
-
businessIntent: ${singleQuoted(`验证${moduleLabel}的${label}子表删除选中行后,数据应被真正移除。`)},
|
|
641
|
-
elementActions: ['进入列表页', '创建带窗口信息子表的主记录', '打开编辑页', '勾选子表行', '点击删除选中', '确认删除', '校验子表行已移除'],
|
|
642
|
-
assertionExpectation: ${singleQuoted(`确认删除后,${label}子表的数据行数应为 0,被删除的数据不再存在。`)},
|
|
643
|
-
playwrightTestTitle: ${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-删除行`)},
|
|
644
|
-
}),
|
|
645
|
-
async () => {
|
|
646
|
-
await openModule(page);
|
|
647
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
648
|
-
const childWorkflowData = crudPage.prepareWorkflowData();
|
|
649
|
-
|
|
650
|
-
// 前置:创建带${label}子表的主记录
|
|
651
|
-
await crudPage.runCreateWorkflowWithChildGrid(childWorkflowData, async (addContext) => {
|
|
652
|
-
const childGridPage = new ChildGridPage(page, addContext, childGridSlots[0]);
|
|
653
|
-
await childGridPage.addRow();
|
|
654
|
-
${createFillSteps}
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
// 打开编辑页
|
|
658
|
-
const editActionLocator = ${locatorLiteral(crudSlots.locators.listEditActionLocatorTemplate)}.replace('{{value}}', childWorkflowData.searchValue);
|
|
659
|
-
await page.locator(editActionLocator).click();
|
|
660
|
-
const editFrame = page.locator(\`iframe[src*="${crudSlots.frames.editIframeSrcKeyword}"]\`).contentFrame();
|
|
661
|
-
|
|
662
|
-
const childGridPage = new ChildGridPage(page, editFrame, childGridSlots[0]);
|
|
663
|
-
const confirmPromise = childGridPage.acceptCustomConfirm('确定要删除');
|
|
664
|
-
await childGridPage.selectRow(1);
|
|
665
|
-
await childGridPage.deleteSelected();
|
|
666
|
-
|
|
667
|
-
await confirmPromise;
|
|
668
|
-
|
|
669
|
-
// 断言删除成功:子表数据行数应为 0
|
|
670
|
-
await page.waitForTimeout(500);
|
|
671
|
-
await childGridPage.assertRowCount(0);
|
|
672
|
-
},
|
|
673
|
-
);
|
|
633
|
+
return ` test(${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-删除行`)}, async ({ page }) => {
|
|
634
|
+
test.slow();
|
|
635
|
+
|
|
636
|
+
await runRecordedWorkflow(
|
|
637
|
+
recorder,
|
|
638
|
+
workflowMeta({
|
|
639
|
+
workflowId: ${singleQuoted(`${moduleId}.childGrid.${slot.gridId}.delete`)},
|
|
640
|
+
workflowKind: 'delete',
|
|
641
|
+
workflowName: ${singleQuoted(caseRef?.title ?? `${label}-删除行`)},
|
|
642
|
+
businessIntent: ${singleQuoted(`验证${moduleLabel}的${label}子表删除选中行后,数据应被真正移除。`)},
|
|
643
|
+
elementActions: ['进入列表页', '创建带窗口信息子表的主记录', '打开编辑页', '勾选子表行', '点击删除选中', '确认删除', '校验子表行已移除'],
|
|
644
|
+
assertionExpectation: ${singleQuoted(`确认删除后,${label}子表的数据行数应为 0,被删除的数据不再存在。`)},
|
|
645
|
+
playwrightTestTitle: ${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-删除行`)},
|
|
646
|
+
}),
|
|
647
|
+
async () => {
|
|
648
|
+
await openModule(page);
|
|
649
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
650
|
+
const childWorkflowData = crudPage.prepareWorkflowData();
|
|
651
|
+
|
|
652
|
+
// 前置:创建带${label}子表的主记录
|
|
653
|
+
await crudPage.runCreateWorkflowWithChildGrid(childWorkflowData, async (addContext) => {
|
|
654
|
+
const childGridPage = new ChildGridPage(page, addContext, childGridSlots[0]);
|
|
655
|
+
await childGridPage.addRow();
|
|
656
|
+
${createFillSteps}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// 打开编辑页
|
|
660
|
+
const editActionLocator = ${locatorLiteral(crudSlots.locators.listEditActionLocatorTemplate)}.replace('{{value}}', childWorkflowData.searchValue);
|
|
661
|
+
await page.locator(editActionLocator).click();
|
|
662
|
+
const editFrame = page.locator(\`iframe[src*="${crudSlots.frames.editIframeSrcKeyword}"]\`).contentFrame();
|
|
663
|
+
|
|
664
|
+
const childGridPage = new ChildGridPage(page, editFrame, childGridSlots[0]);
|
|
665
|
+
const confirmPromise = childGridPage.acceptCustomConfirm('确定要删除');
|
|
666
|
+
await childGridPage.selectRow(1);
|
|
667
|
+
await childGridPage.deleteSelected();
|
|
668
|
+
|
|
669
|
+
await confirmPromise;
|
|
670
|
+
|
|
671
|
+
// 断言删除成功:子表数据行数应为 0
|
|
672
|
+
await page.waitForTimeout(500);
|
|
673
|
+
await childGridPage.assertRowCount(0);
|
|
674
|
+
},
|
|
675
|
+
);
|
|
674
676
|
});`;
|
|
675
677
|
}
|
|
676
678
|
/**
|
|
@@ -680,39 +682,39 @@ function renderChildGridRequiredTest(slot, moduleLabel, moduleId, caseRef) {
|
|
|
680
682
|
const label = slot.label;
|
|
681
683
|
const requiredFields = slot.columns.filter((c) => c.required);
|
|
682
684
|
const requiredLabels = requiredFields.map((c) => c.label).join('、');
|
|
683
|
-
return ` test(${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-必填校验`)}, async ({ page }) => {
|
|
684
|
-
test.slow();
|
|
685
|
-
|
|
686
|
-
await runRecordedWorkflow(
|
|
687
|
-
recorder,
|
|
688
|
-
workflowMeta({
|
|
689
|
-
workflowId: ${singleQuoted(`${moduleId}.childGrid.${slot.gridId}.required`)},
|
|
690
|
-
workflowKind: 'create',
|
|
691
|
-
workflowName: ${singleQuoted(caseRef?.title ?? `${label}-必填校验`)},
|
|
692
|
-
businessIntent: ${singleQuoted(`验证${label}子表必填字段未填写时,主表保存应被阻断并给出校验提示。`)},
|
|
693
|
-
elementActions: ['进入新增页', '点击子表新增行', '不填必填字段', '保存主表', '观察校验提示'],
|
|
694
|
-
assertionExpectation: ${singleQuoted(`系统应阻断保存并提示「${requiredLabels}不能为空」。`)},
|
|
695
|
-
playwrightTestTitle: ${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-必填校验`)},
|
|
696
|
-
}),
|
|
697
|
-
async () => {
|
|
698
|
-
await openModule(page);
|
|
699
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
700
|
-
const childWorkflowData = crudPage.prepareWorkflowData();
|
|
701
|
-
|
|
702
|
-
let alertPromise: Promise<void> | undefined;
|
|
703
|
-
|
|
704
|
-
await crudPage.runCreateWorkflowWithChildGridWithoutSearch(childWorkflowData, async (addContext) => {
|
|
705
|
-
const childGridPage = new ChildGridPage(page, addContext, childGridSlots[0]);
|
|
706
|
-
alertPromise = childGridPage.assertAlert('不能为空');
|
|
707
|
-
await childGridPage.addRow();
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
if (!alertPromise) {
|
|
711
|
-
throw new Error('alertPromise 未初始化');
|
|
712
|
-
}
|
|
713
|
-
await alertPromise;
|
|
714
|
-
},
|
|
715
|
-
);
|
|
685
|
+
return ` test(${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-必填校验`)}, async ({ page }) => {
|
|
686
|
+
test.slow();
|
|
687
|
+
|
|
688
|
+
await runRecordedWorkflow(
|
|
689
|
+
recorder,
|
|
690
|
+
workflowMeta({
|
|
691
|
+
workflowId: ${singleQuoted(`${moduleId}.childGrid.${slot.gridId}.required`)},
|
|
692
|
+
workflowKind: 'create',
|
|
693
|
+
workflowName: ${singleQuoted(caseRef?.title ?? `${label}-必填校验`)},
|
|
694
|
+
businessIntent: ${singleQuoted(`验证${label}子表必填字段未填写时,主表保存应被阻断并给出校验提示。`)},
|
|
695
|
+
elementActions: ['进入新增页', '点击子表新增行', '不填必填字段', '保存主表', '观察校验提示'],
|
|
696
|
+
assertionExpectation: ${singleQuoted(`系统应阻断保存并提示「${requiredLabels}不能为空」。`)},
|
|
697
|
+
playwrightTestTitle: ${singleQuoted(caseRef ? `${caseRef.caseId} ${caseRef.title}` : `${label}-必填校验`)},
|
|
698
|
+
}),
|
|
699
|
+
async () => {
|
|
700
|
+
await openModule(page);
|
|
701
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
702
|
+
const childWorkflowData = crudPage.prepareWorkflowData();
|
|
703
|
+
|
|
704
|
+
let alertPromise: Promise<void> | undefined;
|
|
705
|
+
|
|
706
|
+
await crudPage.runCreateWorkflowWithChildGridWithoutSearch(childWorkflowData, async (addContext) => {
|
|
707
|
+
const childGridPage = new ChildGridPage(page, addContext, childGridSlots[0]);
|
|
708
|
+
alertPromise = childGridPage.assertAlert('不能为空');
|
|
709
|
+
await childGridPage.addRow();
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
if (!alertPromise) {
|
|
713
|
+
throw new Error('alertPromise 未初始化');
|
|
714
|
+
}
|
|
715
|
+
await alertPromise;
|
|
716
|
+
},
|
|
717
|
+
);
|
|
716
718
|
});`;
|
|
717
719
|
}
|
|
718
720
|
/**
|
|
@@ -740,14 +742,28 @@ function renderChildGridTests(slots, crudSlots, moduleLabel, moduleId, glueCases
|
|
|
740
742
|
})
|
|
741
743
|
.join('\n\n');
|
|
742
744
|
}
|
|
743
|
-
/** 解析 glue CRUD 用例标题:`${moduleName} ${actionKind}(${queryKey})`。
|
|
745
|
+
/** 解析 glue CRUD 用例标题:`${moduleName} ${actionKind}(${queryKey})`。
|
|
746
|
+
* 条件性删除用例标题带变体后缀:`delete(${queryKey}-草稿)` / `delete(${queryKey}-在用)`,
|
|
747
|
+
* 解析时剥离后缀还原 base queryKey(保证数据分组不受后缀影响),并返回 deleteVariant。 */
|
|
744
748
|
function parseCrudCaseTitle(title) {
|
|
745
749
|
const match = title.match(/\s(create|read|update|delete)((.+))\s*$/);
|
|
746
750
|
if (!match)
|
|
747
751
|
return null;
|
|
752
|
+
const actionKind = match[1];
|
|
753
|
+
const rawKey = match[2];
|
|
754
|
+
if (actionKind === 'delete') {
|
|
755
|
+
const variantMatch = rawKey.match(/^(.*)-(?:草稿|在用)$/);
|
|
756
|
+
if (variantMatch) {
|
|
757
|
+
return {
|
|
758
|
+
actionKind,
|
|
759
|
+
queryKeyLabel: variantMatch[1],
|
|
760
|
+
deleteVariant: rawKey.endsWith('-草稿') ? 'draft_success' : 'blocked',
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
}
|
|
748
764
|
return {
|
|
749
|
-
actionKind
|
|
750
|
-
queryKeyLabel:
|
|
765
|
+
actionKind,
|
|
766
|
+
queryKeyLabel: rawKey,
|
|
751
767
|
};
|
|
752
768
|
}
|
|
753
769
|
/** 把用例 steps 文本("1. …。 2. …。")拆成 elementActions 数组。 */
|
|
@@ -785,68 +801,89 @@ function renderCrudCaseTests(cases, context) {
|
|
|
785
801
|
let body;
|
|
786
802
|
switch (testCase.actionKind) {
|
|
787
803
|
case 'create':
|
|
788
|
-
body = ` await openModule(page);
|
|
789
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
790
|
-
const data = crudPage.prepareWorkflowData();
|
|
791
|
-
workflowDataByKey[${singleQuoted(key)}] = data;
|
|
804
|
+
body = ` await openModule(page);
|
|
805
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
806
|
+
const data = crudPage.prepareWorkflowData();
|
|
807
|
+
workflowDataByKey[${singleQuoted(key)}] = data;
|
|
792
808
|
await crudPage.runCreateWorkflow(data);`;
|
|
793
809
|
break;
|
|
794
810
|
case 'read':
|
|
795
|
-
body = ` await openModule(page);
|
|
796
|
-
const data = workflowDataByKey[${singleQuoted(dataKey)}];
|
|
797
|
-
if (!data) {
|
|
798
|
-
throw new Error(${singleQuoted(`${testCase.title} 依赖 create(${dataKey})用例先行生成 workflowData`)});
|
|
799
|
-
}
|
|
800
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
811
|
+
body = ` await openModule(page);
|
|
812
|
+
const data = workflowDataByKey[${singleQuoted(dataKey)}];
|
|
813
|
+
if (!data) {
|
|
814
|
+
throw new Error(${singleQuoted(`${testCase.title} 依赖 create(${dataKey})用例先行生成 workflowData`)});
|
|
815
|
+
}
|
|
816
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
801
817
|
await crudPage.runReadWorkflowByField(data, ${singleQuoted(key)});`;
|
|
802
818
|
break;
|
|
803
819
|
case 'update':
|
|
804
|
-
body = ` await openModule(page);
|
|
805
|
-
const data = workflowDataByKey[${singleQuoted(dataKey)}];
|
|
806
|
-
if (!data) {
|
|
807
|
-
throw new Error(${singleQuoted(`${testCase.title} 依赖 create(${dataKey})用例先行生成 workflowData`)});
|
|
808
|
-
}
|
|
809
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
820
|
+
body = ` await openModule(page);
|
|
821
|
+
const data = workflowDataByKey[${singleQuoted(dataKey)}];
|
|
822
|
+
if (!data) {
|
|
823
|
+
throw new Error(${singleQuoted(`${testCase.title} 依赖 create(${dataKey})用例先行生成 workflowData`)});
|
|
824
|
+
}
|
|
825
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
810
826
|
${componentForLabel(key) === 'listbox'
|
|
811
827
|
? ` await crudPage.runUpdateListboxWorkflow(data, ${singleQuoted(key)});`
|
|
812
828
|
: ` await crudPage.runUpdateWorkflow(data);`}`;
|
|
813
829
|
break;
|
|
814
830
|
case 'delete':
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
const
|
|
831
|
+
if (testCase.deleteVariant === 'draft_success') {
|
|
832
|
+
// 条件性删除-可删用例:自建草稿数据(saveMode='draft')再删除,预期成功。
|
|
833
|
+
// 不复用 create 组的在用数据(deleteVariant='failed' 的用例依赖它保持未被删除)。
|
|
834
|
+
body = ` await openModule(page);
|
|
835
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
836
|
+
const data = crudPage.prepareWorkflowData();
|
|
837
|
+
await crudPage.runCreateWorkflow(data, { saveMode: 'draft' });
|
|
838
|
+
await crudPage.runDeleteWorkflow(data, { expectation: 'success' });`;
|
|
839
|
+
}
|
|
840
|
+
else if (testCase.deleteVariant === 'blocked') {
|
|
841
|
+
// 条件性删除-阻断用例:复用 create 组的在用数据,预期删除失败。
|
|
842
|
+
body = ` await openModule(page);
|
|
843
|
+
const data = workflowDataByKey[${singleQuoted(dataKey)}];
|
|
844
|
+
if (!data) {
|
|
845
|
+
throw new Error(${singleQuoted(`${testCase.title} 依赖 create(${dataKey})用例先行生成 workflowData`)});
|
|
846
|
+
}
|
|
847
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
848
|
+
await crudPage.runDeleteWorkflow(data, { expectation: 'failed' });`;
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
body = ` await openModule(page);
|
|
852
|
+
const data = workflowDataByKey[${singleQuoted(dataKey)}];
|
|
853
|
+
if (!data) {
|
|
854
|
+
throw new Error(${singleQuoted(`${testCase.title} 依赖 create(${dataKey})用例先行生成 workflowData`)});
|
|
855
|
+
}
|
|
856
|
+
const crudPage = new CrudPage(page, crudSlots);
|
|
821
857
|
await crudPage.runDeleteWorkflow(data);`;
|
|
858
|
+
}
|
|
822
859
|
break;
|
|
823
860
|
}
|
|
824
|
-
return ` test(${singleQuoted(fullTitle)}, async ({ page }) => {
|
|
825
|
-
test.slow();
|
|
826
|
-
|
|
827
|
-
await runRecordedWorkflow(
|
|
828
|
-
recorder,
|
|
829
|
-
workflowMeta({
|
|
830
|
-
workflowId: ${singleQuoted(`${moduleId}.${testCase.caseId}`)},
|
|
831
|
-
workflowKind: '${workflowKind}',
|
|
832
|
-
workflowName: ${singleQuoted(testCase.title)},
|
|
833
|
-
businessIntent: ${singleQuoted(businessIntent)},
|
|
834
|
-
elementActions: [${elementActions}],
|
|
835
|
-
assertionExpectation: ${singleQuoted(assertionExpectation)},
|
|
836
|
-
playwrightTestTitle: ${singleQuoted(fullTitle)},
|
|
837
|
-
}),
|
|
838
|
-
async () => {
|
|
839
|
-
${body}
|
|
840
|
-
},
|
|
841
|
-
);
|
|
861
|
+
return ` test(${singleQuoted(fullTitle)}, async ({ page }) => {
|
|
862
|
+
test.slow();
|
|
863
|
+
|
|
864
|
+
await runRecordedWorkflow(
|
|
865
|
+
recorder,
|
|
866
|
+
workflowMeta({
|
|
867
|
+
workflowId: ${singleQuoted(`${moduleId}.${testCase.caseId}`)},
|
|
868
|
+
workflowKind: '${workflowKind}',
|
|
869
|
+
workflowName: ${singleQuoted(testCase.title)},
|
|
870
|
+
businessIntent: ${singleQuoted(businessIntent)},
|
|
871
|
+
elementActions: [${elementActions}],
|
|
872
|
+
assertionExpectation: ${singleQuoted(assertionExpectation)},
|
|
873
|
+
playwrightTestTitle: ${singleQuoted(fullTitle)},
|
|
874
|
+
}),
|
|
875
|
+
async () => {
|
|
876
|
+
${body}
|
|
877
|
+
},
|
|
878
|
+
);
|
|
842
879
|
});`;
|
|
843
880
|
});
|
|
844
|
-
return ` test.describe('主表 CRUD 流程', () => {
|
|
845
|
-
test.describe.configure({ mode: 'serial' });
|
|
846
|
-
const workflowDataByKey: Record<string, CrudWorkflowData | undefined> = {};
|
|
847
|
-
|
|
848
|
-
${blocks.join('\n\n')}
|
|
849
|
-
|
|
881
|
+
return ` test.describe('主表 CRUD 流程', () => {
|
|
882
|
+
test.describe.configure({ mode: 'serial' });
|
|
883
|
+
const workflowDataByKey: Record<string, CrudWorkflowData | undefined> = {};
|
|
884
|
+
|
|
885
|
+
${blocks.join('\n\n')}
|
|
886
|
+
|
|
850
887
|
});`;
|
|
851
888
|
}
|
|
852
889
|
/**
|
|
@@ -856,18 +893,18 @@ ${blocks.join('\n\n')}
|
|
|
856
893
|
* @returns TypeScript 对象字面量源码
|
|
857
894
|
*/
|
|
858
895
|
function renderExportSlots(contract) {
|
|
859
|
-
return `{
|
|
860
|
-
moduleId: ${singleQuoted(contract.module.id)},
|
|
861
|
-
frames: {
|
|
862
|
-
listIframeSrcKeyword: ${singleQuoted(iframeKeyword(contract.pages.list.iframeSrcKeyword, true))},
|
|
863
|
-
},
|
|
864
|
-
locators: {
|
|
865
|
-
exportButton: '.mini-button.mini-dataexport',
|
|
866
|
-
},
|
|
867
|
-
export: {
|
|
868
|
-
label: '导出',
|
|
869
|
-
mode: 'all' as const,
|
|
870
|
-
},
|
|
896
|
+
return `{
|
|
897
|
+
moduleId: ${singleQuoted(contract.module.id)},
|
|
898
|
+
frames: {
|
|
899
|
+
listIframeSrcKeyword: ${singleQuoted(iframeKeyword(contract.pages.list.iframeSrcKeyword, true))},
|
|
900
|
+
},
|
|
901
|
+
locators: {
|
|
902
|
+
exportButton: '.mini-button.mini-dataexport',
|
|
903
|
+
},
|
|
904
|
+
export: {
|
|
905
|
+
label: '导出',
|
|
906
|
+
mode: 'all' as const,
|
|
907
|
+
},
|
|
871
908
|
}`;
|
|
872
909
|
}
|
|
873
910
|
/**
|
|
@@ -883,18 +920,18 @@ function renderExportSlots(contract) {
|
|
|
883
920
|
* @returns TypeScript 对象字面量源码
|
|
884
921
|
*/
|
|
885
922
|
function renderExportSlotsFromSlot(exportSlot, listIframeSrcKeyword) {
|
|
886
|
-
return `{
|
|
887
|
-
moduleId: ${singleQuoted(exportSlot.moduleId)},
|
|
888
|
-
frames: {
|
|
889
|
-
listIframeSrcKeyword: ${singleQuoted(listIframeSrcKeyword)},
|
|
890
|
-
},
|
|
891
|
-
locators: {
|
|
892
|
-
exportButton: '.mini-button.mini-dataexport',
|
|
893
|
-
},
|
|
894
|
-
export: {
|
|
895
|
-
label: ${singleQuoted(exportSlot.label)},
|
|
896
|
-
mode: ${exportSlot.mode === 'all' ? "'all' as const" : singleQuoted(exportSlot.mode)},
|
|
897
|
-
},
|
|
923
|
+
return `{
|
|
924
|
+
moduleId: ${singleQuoted(exportSlot.moduleId)},
|
|
925
|
+
frames: {
|
|
926
|
+
listIframeSrcKeyword: ${singleQuoted(listIframeSrcKeyword)},
|
|
927
|
+
},
|
|
928
|
+
locators: {
|
|
929
|
+
exportButton: '.mini-button.mini-dataexport',
|
|
930
|
+
},
|
|
931
|
+
export: {
|
|
932
|
+
label: ${singleQuoted(exportSlot.label)},
|
|
933
|
+
mode: ${exportSlot.mode === 'all' ? "'all' as const" : singleQuoted(exportSlot.mode)},
|
|
934
|
+
},
|
|
898
935
|
}`;
|
|
899
936
|
}
|
|
900
937
|
/**
|
|
@@ -940,102 +977,102 @@ export function generateStageSkeletonCrudSpecFromSlots(options) {
|
|
|
940
977
|
}
|
|
941
978
|
const crudCaseTests = renderCrudCaseTests(crudCases, { moduleId, moduleLabel, componentForLabel });
|
|
942
979
|
const exportCaseRef = (glueCases ?? []).find((testCase) => testCase.title.endsWith(' export'));
|
|
943
|
-
return `import { test, expect, type Page } from '@playwright/test';
|
|
944
|
-
import { loadCredentials, StagePath, type Credential } from '@epoint-testtech/stage-core';
|
|
945
|
-
import { CrudPage, ExportPage, LoginPage${childGridImport}, type CrudFlowSlots, type ExportFlowSlots, type CrudWorkflowData } from '../../skeletons';
|
|
946
|
-
import { GlueWorkflowRecorder, runRecordedWorkflow, type GlueWorkflowRecordInput } from '../../report/glue-report';
|
|
947
|
-
|
|
948
|
-
test.describe(${singleQuoted(suiteTitle)}, () => {
|
|
949
|
-
const directListUrl = ${singleQuoted(directUrl)};
|
|
950
|
-
const credentialsPath = \`\${StagePath.projectRootDirPath}/src/tests/${requirementName}/credentials.json5\`;
|
|
951
|
-
const primaryRole = ${singleQuoted(primaryRole)};
|
|
952
|
-
const crudSlots: CrudFlowSlots = ${renderStageSkeletonCrudSlots(crudSlots)};
|
|
953
|
-
const exportSlots = ${renderExportSlotsFromSlot(exportSlot, crudSlots.frames.listIframeSrcKeyword)};
|
|
954
|
-
${childGridSlots?.length ? ` const childGridSlots: ChildGridFlowSlots[] = ${renderChildGridSlots(childGridSlots)};` : ''}
|
|
955
|
-
|
|
956
|
-
const recorder = new GlueWorkflowRecorder({
|
|
957
|
-
outputRoot: StagePath.projectRootDirPath,
|
|
958
|
-
moduleId: ${singleQuoted(moduleId)},
|
|
959
|
-
moduleName: ${singleQuoted(moduleLabel)},
|
|
960
|
-
suiteTitle: ${singleQuoted(suiteTitle)},
|
|
961
|
-
generatedSpecFile: import.meta.url,
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
test.beforeAll(async ({ browser }) => {
|
|
965
|
-
recorder.updateEnvironment({
|
|
966
|
-
browserName: browser.browserType().name(),
|
|
967
|
-
browserVersion: browser.version(),
|
|
968
|
-
});
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
test.afterAll(() => {
|
|
972
|
-
recorder.writeReport();
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
function resolveCredential(role: string): Credential {
|
|
976
|
-
const [credential] = loadCredentials(credentialsPath, role);
|
|
977
|
-
if (!credential) {
|
|
978
|
-
throw new Error(\`credentials.json5 中未找到角色: \${role}\`);
|
|
979
|
-
}
|
|
980
|
-
return credential;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
function applyLoginCredential(credential: Credential): void {
|
|
984
|
-
process.env.LOGIN_SYSTEM_URL = credential.url;
|
|
985
|
-
process.env.LOGIN_USERNAME = credential.username;
|
|
986
|
-
process.env.LOGIN_PASSWORD = credential.password;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
async function openModule(page: Page): Promise<void> {
|
|
990
|
-
applyLoginCredential(resolveCredential(primaryRole));
|
|
991
|
-
const loginPage = new LoginPage(page);
|
|
992
|
-
await loginPage.login();
|
|
993
|
-
await loginPage.gotoDirectUrl(directListUrl);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
function workflowMeta(
|
|
997
|
-
input: Omit<
|
|
998
|
-
GlueWorkflowRecordInput,
|
|
999
|
-
'moduleId' | 'executionSource' | 'evidenceSources' | 'evidenceRefs' | 'playwrightSuiteTitle'
|
|
1000
|
-
>,
|
|
1001
|
-
): GlueWorkflowRecordInput {
|
|
1002
|
-
return {
|
|
1003
|
-
moduleId: ${singleQuoted(moduleId)},
|
|
1004
|
-
executionSource: 'glue_code' as const,
|
|
1005
|
-
evidenceSources: ['crud_flow_slots'],
|
|
1006
|
-
evidenceRefs: [],
|
|
1007
|
-
playwrightSuiteTitle: ${singleQuoted(suiteTitle)},
|
|
1008
|
-
...input,
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
${crudCaseTests}
|
|
1012
|
-
|
|
1013
|
-
test(${singleQuoted(exportCaseRef ? `${exportCaseRef.caseId} ${exportCaseRef.title}` : `Export:导出${moduleLabel}列表并校验下载文件`)}, async ({ page }, testInfo) => {
|
|
1014
|
-
test.slow();
|
|
1015
|
-
|
|
1016
|
-
await runRecordedWorkflow(
|
|
1017
|
-
recorder,
|
|
1018
|
-
workflowMeta({
|
|
1019
|
-
workflowId: ${singleQuoted(exportCaseRef ? `${moduleId}.${exportCaseRef.caseId}` : `${moduleId}.export`)},
|
|
1020
|
-
workflowKind: 'export',
|
|
1021
|
-
workflowName: ${singleQuoted(exportCaseRef ? exportCaseRef.title : `导出${moduleLabel}列表`)},
|
|
1022
|
-
businessIntent: '在列表页触发全部导出,验证导出弹框可驱动并成功下载数据文件。',
|
|
1023
|
-
elementActions: ['点击列表页数据导出按钮打开导出弹框', '在导出弹框选择「全部」并确认导出', '等待 download 事件并断言下载文件存在且非空'],
|
|
1024
|
-
assertionExpectation: '导出操作应触发下载,落盘文件存在且大小大于 0。',
|
|
1025
|
-
playwrightTestTitle: ${singleQuoted(exportCaseRef ? `${exportCaseRef.caseId} ${exportCaseRef.title}` : `Export:导出${moduleLabel}列表并校验下载文件`)},
|
|
1026
|
-
}),
|
|
1027
|
-
async () => {
|
|
1028
|
-
await openModule(page);
|
|
1029
|
-
const exportPage = new ExportPage(page, exportSlots);
|
|
1030
|
-
await exportPage.runExportWorkflow(testInfo);
|
|
1031
|
-
},
|
|
1032
|
-
);
|
|
1033
|
-
});
|
|
1034
|
-
|
|
1035
|
-
${childGridSlots?.length ? renderChildGridTests(childGridSlots, crudSlots, moduleLabel, moduleId, glueCases) : ''}
|
|
1036
|
-
|
|
1037
|
-
// @stage-gap-append
|
|
1038
|
-
});
|
|
980
|
+
return `import { test, expect, type Page } from '@playwright/test';
|
|
981
|
+
import { loadCredentials, StagePath, type Credential } from '@epoint-testtech/stage-core';
|
|
982
|
+
import { CrudPage, ExportPage, LoginPage${childGridImport}, type CrudFlowSlots, type ExportFlowSlots, type CrudWorkflowData } from '../../skeletons';
|
|
983
|
+
import { GlueWorkflowRecorder, runRecordedWorkflow, type GlueWorkflowRecordInput } from '../../report/glue-report';
|
|
984
|
+
|
|
985
|
+
test.describe(${singleQuoted(suiteTitle)}, () => {
|
|
986
|
+
const directListUrl = ${singleQuoted(directUrl)};
|
|
987
|
+
const credentialsPath = \`\${StagePath.projectRootDirPath}/src/tests/${requirementName}/credentials.json5\`;
|
|
988
|
+
const primaryRole = ${singleQuoted(primaryRole)};
|
|
989
|
+
const crudSlots: CrudFlowSlots = ${renderStageSkeletonCrudSlots(crudSlots)};
|
|
990
|
+
const exportSlots = ${renderExportSlotsFromSlot(exportSlot, crudSlots.frames.listIframeSrcKeyword)};
|
|
991
|
+
${childGridSlots?.length ? ` const childGridSlots: ChildGridFlowSlots[] = ${renderChildGridSlots(childGridSlots)};` : ''}
|
|
992
|
+
|
|
993
|
+
const recorder = new GlueWorkflowRecorder({
|
|
994
|
+
outputRoot: StagePath.projectRootDirPath,
|
|
995
|
+
moduleId: ${singleQuoted(moduleId)},
|
|
996
|
+
moduleName: ${singleQuoted(moduleLabel)},
|
|
997
|
+
suiteTitle: ${singleQuoted(suiteTitle)},
|
|
998
|
+
generatedSpecFile: import.meta.url,
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
test.beforeAll(async ({ browser }) => {
|
|
1002
|
+
recorder.updateEnvironment({
|
|
1003
|
+
browserName: browser.browserType().name(),
|
|
1004
|
+
browserVersion: browser.version(),
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
test.afterAll(() => {
|
|
1009
|
+
recorder.writeReport();
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
function resolveCredential(role: string): Credential {
|
|
1013
|
+
const [credential] = loadCredentials(credentialsPath, role);
|
|
1014
|
+
if (!credential) {
|
|
1015
|
+
throw new Error(\`credentials.json5 中未找到角色: \${role}\`);
|
|
1016
|
+
}
|
|
1017
|
+
return credential;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function applyLoginCredential(credential: Credential): void {
|
|
1021
|
+
process.env.LOGIN_SYSTEM_URL = credential.url;
|
|
1022
|
+
process.env.LOGIN_USERNAME = credential.username;
|
|
1023
|
+
process.env.LOGIN_PASSWORD = credential.password;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
async function openModule(page: Page): Promise<void> {
|
|
1027
|
+
applyLoginCredential(resolveCredential(primaryRole));
|
|
1028
|
+
const loginPage = new LoginPage(page);
|
|
1029
|
+
await loginPage.login();
|
|
1030
|
+
await loginPage.gotoDirectUrl(directListUrl);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function workflowMeta(
|
|
1034
|
+
input: Omit<
|
|
1035
|
+
GlueWorkflowRecordInput,
|
|
1036
|
+
'moduleId' | 'executionSource' | 'evidenceSources' | 'evidenceRefs' | 'playwrightSuiteTitle'
|
|
1037
|
+
>,
|
|
1038
|
+
): GlueWorkflowRecordInput {
|
|
1039
|
+
return {
|
|
1040
|
+
moduleId: ${singleQuoted(moduleId)},
|
|
1041
|
+
executionSource: 'glue_code' as const,
|
|
1042
|
+
evidenceSources: ['crud_flow_slots'],
|
|
1043
|
+
evidenceRefs: [],
|
|
1044
|
+
playwrightSuiteTitle: ${singleQuoted(suiteTitle)},
|
|
1045
|
+
...input,
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
${crudCaseTests}
|
|
1049
|
+
|
|
1050
|
+
test(${singleQuoted(exportCaseRef ? `${exportCaseRef.caseId} ${exportCaseRef.title}` : `Export:导出${moduleLabel}列表并校验下载文件`)}, async ({ page }, testInfo) => {
|
|
1051
|
+
test.slow();
|
|
1052
|
+
|
|
1053
|
+
await runRecordedWorkflow(
|
|
1054
|
+
recorder,
|
|
1055
|
+
workflowMeta({
|
|
1056
|
+
workflowId: ${singleQuoted(exportCaseRef ? `${moduleId}.${exportCaseRef.caseId}` : `${moduleId}.export`)},
|
|
1057
|
+
workflowKind: 'export',
|
|
1058
|
+
workflowName: ${singleQuoted(exportCaseRef ? exportCaseRef.title : `导出${moduleLabel}列表`)},
|
|
1059
|
+
businessIntent: '在列表页触发全部导出,验证导出弹框可驱动并成功下载数据文件。',
|
|
1060
|
+
elementActions: ['点击列表页数据导出按钮打开导出弹框', '在导出弹框选择「全部」并确认导出', '等待 download 事件并断言下载文件存在且非空'],
|
|
1061
|
+
assertionExpectation: '导出操作应触发下载,落盘文件存在且大小大于 0。',
|
|
1062
|
+
playwrightTestTitle: ${singleQuoted(exportCaseRef ? `${exportCaseRef.caseId} ${exportCaseRef.title}` : `Export:导出${moduleLabel}列表并校验下载文件`)},
|
|
1063
|
+
}),
|
|
1064
|
+
async () => {
|
|
1065
|
+
await openModule(page);
|
|
1066
|
+
const exportPage = new ExportPage(page, exportSlots);
|
|
1067
|
+
await exportPage.runExportWorkflow(testInfo);
|
|
1068
|
+
},
|
|
1069
|
+
);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
${childGridSlots?.length ? renderChildGridTests(childGridSlots, crudSlots, moduleLabel, moduleId, glueCases) : ''}
|
|
1073
|
+
|
|
1074
|
+
// @stage-gap-append
|
|
1075
|
+
});
|
|
1039
1076
|
`;
|
|
1040
1077
|
}
|
|
1041
1078
|
/**
|