openxiangda-cli 2.0.0-alpha.172 → 2.0.0-alpha.173
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/package.json +5 -5
- package/template/apps/web/e2e/mobile-files.spec.ts +3 -0
- package/template/apps/web/e2e/resources.spec.ts +4 -1
- package/template/apps/web/e2e/standard-admin.spec.ts +16 -4
- package/template/apps/web/e2e/workflow-entry-fixture.tsx +2 -2
- package/template/apps/web/e2e/workflow-entry.spec.ts +82 -43
- package/template/apps/web/e2e/workflow.spec.ts +67 -17
- package/template/apps/web/package.json +1 -1
- package/template/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda-cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.173",
|
|
4
4
|
"description": "Thin application-level CLI for OpenXiangda 2.0.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@oclif/core": "4.13.3",
|
|
24
|
-
"openxiangda-contracts": "2.0.0-alpha.
|
|
25
|
-
"openxiangda-devkit-core": "2.0.0-alpha.
|
|
26
|
-
"openxiangda-mcp": "2.0.0-alpha.
|
|
27
|
-
"openxiangda-skill-kit": "2.0.0-alpha.
|
|
24
|
+
"openxiangda-contracts": "2.0.0-alpha.87",
|
|
25
|
+
"openxiangda-devkit-core": "2.0.0-alpha.111",
|
|
26
|
+
"openxiangda-mcp": "2.0.0-alpha.111",
|
|
27
|
+
"openxiangda-skill-kit": "2.0.0-alpha.136"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"tsx": "4.23.12",
|
|
@@ -75,6 +75,9 @@ test.describe('mobile managed files', () => {
|
|
|
75
75
|
await expect(page.locator('.adm-image-viewer-indicator')).toHaveText('2 / 2');
|
|
76
76
|
await expect(page.locator('.adm-image-viewer-image-wrapper img').last()).toHaveJSProperty('naturalWidth', 640);
|
|
77
77
|
await page.screenshot({ path: info.outputPath('mobile-image-preview.png') });
|
|
78
|
+
const imageDownload = page.waitForEvent('download');
|
|
79
|
+
await page.getByRole('button', { name: '下载', exact: true }).click();
|
|
80
|
+
expect((await imageDownload).suggestedFilename()).toBe('第二张.png');
|
|
78
81
|
await page.getByRole('button', { name: '关闭预览', exact: true }).click();
|
|
79
82
|
await expect(page.getByRole('button', { name: '关闭预览', exact: true })).toHaveCount(0);
|
|
80
83
|
let downloadAttempts = 0;
|
|
@@ -335,7 +335,10 @@ test('renders canonical semantic labels in the real generated resource list', as
|
|
|
335
335
|
const detail = page.getByRole('dialog');
|
|
336
336
|
await expect(detail).toBeVisible();
|
|
337
337
|
await expect(detail).toContainText('组织信息演示');
|
|
338
|
-
|
|
338
|
+
const frame = detail.locator('[data-detail-frame="standard"]');
|
|
339
|
+
await expect(frame).toBeVisible();
|
|
340
|
+
await expect(frame.locator('.oxa-record-detail-card')).toHaveCount(2);
|
|
341
|
+
await expect(frame.locator('.oxa-record-detail-card').getByRole('heading', { level: 2 })).toHaveText(['栏目设置', '管理信息']);
|
|
339
342
|
});
|
|
340
343
|
|
|
341
344
|
test('renders the canonical first field on the mobile generated list', async ({ page }) => {
|
|
@@ -307,7 +307,7 @@ test('record reads can retry and reconnect refresh does not overwrite an edit or
|
|
|
307
307
|
await page.context().setOffline(false);
|
|
308
308
|
await expect.poll(() => state.reads).toBeGreaterThan(reads);
|
|
309
309
|
await expect(drawer.getByLabel('栏目名称', { exact: true })).toHaveValue('我的修改');
|
|
310
|
-
await drawer.getByRole('button', { name: '
|
|
310
|
+
await drawer.getByRole('button', { name: '保存' }).click();
|
|
311
311
|
await expect(drawer.getByText('保存失败', { exact: true })).toBeVisible();
|
|
312
312
|
expect(state.writes.at(-1).expectedRevision).toBe(1);
|
|
313
313
|
await expect(drawer.getByLabel('栏目名称', { exact: true })).toHaveValue('我的修改');
|
|
@@ -464,18 +464,30 @@ test('direct entry is independent and adapts desktop URLs to touch controls with
|
|
|
464
464
|
await expect(page.getByRole('textbox', { name: /栏目名称/ })).toHaveValue('保留未暂存输入');
|
|
465
465
|
});
|
|
466
466
|
|
|
467
|
-
test('details open readonly in a drawer and closing unsaved entry has no confirmation or write', async ({ page }) => {
|
|
467
|
+
test('details open readonly in a drawer and closing unsaved entry has no confirmation or write', async ({ page }, testInfo) => {
|
|
468
468
|
const state = await editablePlatform(page);
|
|
469
469
|
await page.goto('/resource-experience.e2e.html?primary=business');
|
|
470
470
|
const initialUrl = page.url();
|
|
471
471
|
await page.getByRole('button', { name: '查看', exact: true }).first().click();
|
|
472
472
|
const detail = page.getByRole('dialog', { name: '内容栏目详情' });
|
|
473
473
|
await expect(detail).toBeVisible();
|
|
474
|
-
await expect(detail.locator('.oxa-
|
|
475
|
-
await expect(detail.
|
|
474
|
+
await expect(detail.locator('.oxa-record-detail-sections')).toBeVisible();
|
|
475
|
+
await expect(detail.getByRole('heading', { name: '业务记录 1', exact: true })).toBeVisible();
|
|
476
476
|
await expect(detail.getByRole('textbox')).toHaveCount(0);
|
|
477
477
|
await expect(detail.getByRole('button', { name: '提交', exact: true })).toHaveCount(0);
|
|
478
478
|
expect(page.url()).toBe(initialUrl);
|
|
479
|
+
await expect(detail).toHaveCSS('width', '850px');
|
|
480
|
+
await page.screenshot({ path: testInfo.outputPath('ordinary-detail-drawer-850.png'), animations: 'disabled' });
|
|
481
|
+
await detail.getByRole('button', { name: '编辑', exact: true }).first().click();
|
|
482
|
+
const edit = page.getByRole('dialog', { name: '编辑数据' });
|
|
483
|
+
await expect(page.getByRole('dialog')).toHaveCount(1);
|
|
484
|
+
await expect(edit.locator('.oxa-record-detail.is-editing')).toBeVisible();
|
|
485
|
+
await edit.getByLabel('栏目名称', { exact: true }).fill('普通原地编辑');
|
|
486
|
+
await page.screenshot({ path: testInfo.outputPath('ordinary-detail-inline-edit.png'), animations: 'disabled' });
|
|
487
|
+
await edit.getByRole('button', { name: '关闭', exact: true }).click();
|
|
488
|
+
await expect(detail).toBeVisible();
|
|
489
|
+
await expect(detail.getByRole('heading', { name: '业务记录 1', exact: true })).toBeVisible();
|
|
490
|
+
expect(page.url()).toBe(initialUrl);
|
|
479
491
|
await detail.getByRole('button', { name: '关闭', exact: true }).click();
|
|
480
492
|
await page.getByRole('button', { name: '新增', exact: true }).click();
|
|
481
493
|
const entry = page.getByRole('dialog', { name: '新增数据' });
|
|
@@ -81,7 +81,7 @@ function Entry() {
|
|
|
81
81
|
const [dismissed, setDismissed] = useState(false);
|
|
82
82
|
const location = useLocation();
|
|
83
83
|
if (mode === 'list') return <GeneratedResourcePage resourceCode={code} paths={paths} />;
|
|
84
|
-
if (mode === 'mobile-detail') return <Routes><Route path={paths.detail} element={<GeneratedResourcePage resourceCode={code} mode="detail" variant=
|
|
84
|
+
if (mode === 'mobile-detail' || mode === 'desktop-detail') return <Routes><Route path={paths.detail} element={<GeneratedResourcePage resourceCode={code} mode="detail" variant={mode === 'mobile-detail' ? 'mobile' : 'desktop'} paths={paths} />} /></Routes>;
|
|
85
85
|
return <>
|
|
86
86
|
<button onClick={() => setRender(value => value + 1)}>重新渲染父页面</button>
|
|
87
87
|
<output data-testid="parent-render">{render}</output>
|
|
@@ -94,7 +94,7 @@ function Entry() {
|
|
|
94
94
|
|
|
95
95
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
96
96
|
<React.StrictMode>
|
|
97
|
-
<OpenXiangdaUiProvider><AntdApp><MemoryRouter initialEntries={[mode === 'mobile-detail' ? `${paths.list}/record-1` : paths.list]}><RuntimeBoundary>
|
|
97
|
+
<OpenXiangdaUiProvider><AntdApp><MemoryRouter initialEntries={[(mode === 'mobile-detail' || mode === 'desktop-detail') ? `${paths.list}/record-1` : paths.list]}><RuntimeBoundary>
|
|
98
98
|
<OpenXiangdaResourceDefinitionsProvider definitions={definitions}>
|
|
99
99
|
<OpenXiangdaWorkflowDefinitionsProvider definitions={workflowDefinitions}>
|
|
100
100
|
<Refine dataProvider={createApplicationProvider(definitions)} resources={[{ name: code, list: paths.list, show: paths.detail }]}>
|
|
@@ -58,6 +58,23 @@ async function workflowEntryPlatform(page: Page) {
|
|
|
58
58
|
}
|
|
59
59
|
if (path.endsWith('/auth/surface')) return ok({ csrfToken: 'workflow-entry-csrf' });
|
|
60
60
|
if (path.includes('/native/admin-list/preferences/')) return ok(null);
|
|
61
|
+
if (path.endsWith(`/workflow/records/${resourceCode}/${state.record.id}/detail`)) {
|
|
62
|
+
const childFields = { name: { label: '品名', type: 'text.short', widget: 'text', ...fieldCapabilities }, quantity: { label: '数量', type: 'number.integer', widget: 'number', ...fieldCapabilities }, detailNote: { label: '仅详情说明', type: 'text.short', widget: 'text', ...fieldCapabilities } };
|
|
63
|
+
const fields = { ...correctionFields, decision: { label: '审批结论', type: 'text.short', widget: 'readonly', ...fieldCapabilities }, ...(state.complexFields ? { items: { label: '采购明细', type: 'subtable', widget: 'subtable', subtable: { resourceCode: 'purchase-lines', foreignKey: 'purchaseId', orderField: 'sortOrder', maxRows: 20 }, ...fieldCapabilities } } : {}) };
|
|
64
|
+
const instance = { id: instanceId, appCode, environmentKey: 'preproduction', status: 'approved', outcome: 'approved', startedAt: '2026-09-06T01:00:00Z', completedAt: '2026-09-06T02:38:00Z', dataRef: { resourceCode, recordId: state.record.id } };
|
|
65
|
+
const surface = { schemaVersion: 'openxiangda.workflow-surface/v2', protocolVersion: 'workflow_surface_v2', engineVersion: '2.0', surfaceRevision: 'a'.repeat(64), instanceSequence: 4, instance, task: null, operations: [],
|
|
66
|
+
detailNavigation: { desktopPath: `/admin/purchases/${state.record.id}`, mobilePath: `/m/admin/purchases/${state.record.id}`, custom: false },
|
|
67
|
+
presentation: { summary: { title: '采购申请', initiatorDisplayName: '陈晨', departmentDisplayName: '办公室', submittedAt: instance.startedAt }, businessDetail: {
|
|
68
|
+
status: 'ready', resourceCode, resourceName: '采购申请', recordId: state.record.id, requestedRevision: state.record.revision, sourceRevision: state.record.revision,
|
|
69
|
+
surface: { mutationOwner: 'workflow', fields, detail: { layout: 'sections', fieldOrder: Object.keys(fields) } }, record: state.record,
|
|
70
|
+
subtables: state.complexFields ? { items: { resourceCode: 'purchase-lines', surface: { fields: childFields, detail: { fieldOrder: Object.keys(childFields) } }, rows: state.children, total: state.children.length } } : {}, projectionDigest: 'c'.repeat(64),
|
|
71
|
+
} },
|
|
72
|
+
};
|
|
73
|
+
const timeline = { engineVersion: '2.0', instanceId, instanceSequence: 4, timelineRevision: 'b'.repeat(64), flow: { nodes: [], edges: [] }, items: [], display: { entries: [] } };
|
|
74
|
+
return ok({ schemaVersion: 'openxiangda.workflow-detail-surface/v2', protocolVersion: 'workflow_detail_surface_v2', detailRevision: 'd'.repeat(64), instanceSequence: 4, timelineRevision: timeline.timelineRevision, surface, timeline,
|
|
75
|
+
currentStatus: { code: 'approved', label: '已同意', tone: 'success' }, nodes: [], handlingRecords: [], operations: [], navigationContext: { desktopReturnPath: '/admin/purchases', mobileReturnPath: '/m/admin/purchases' } });
|
|
76
|
+
}
|
|
77
|
+
if (path.endsWith(`/workflow/instances/${instanceId}/data-audit`)) return ok({ schemaVersion: 'openxiangda.data-audit-page/v2', items: [], total: 0, offset: 0, limit: 50 });
|
|
61
78
|
if (path.endsWith('/native/data/purchase-lines/query')) {
|
|
62
79
|
state.childQueries.push(request.postDataJSON());
|
|
63
80
|
return ok({ schemaVersion: 'openxiangda.data-page/v2', resourceCode: 'purchase-lines', items: state.children, total: state.children.length, offset: 0, limit: 20 });
|
|
@@ -185,7 +202,7 @@ async function workflowEntryPlatform(page: Page) {
|
|
|
185
202
|
|
|
186
203
|
async function openCorrection(page: Page, search = '') {
|
|
187
204
|
await page.goto(`/workflow-entry.e2e.html${search}`);
|
|
188
|
-
await page.getByRole('button', { name: '编辑', exact: true }).click();
|
|
205
|
+
await page.getByRole('button', { name: '编辑', exact: true }).first().click();
|
|
189
206
|
const drawer = page.getByRole('dialog', { name: '编辑数据' });
|
|
190
207
|
await expect(drawer.getByLabel('申请名称', { exact: true })).toHaveValue('原业务申请');
|
|
191
208
|
await expect(drawer.getByLabel('申请金额', { exact: true })).toHaveValue('28600');
|
|
@@ -199,14 +216,14 @@ test('ordinary workflow editing uses the shared form and opens readonly detail a
|
|
|
199
216
|
await expect(drawer.getByText(/更正|审计|重新审批/)).toHaveCount(0);
|
|
200
217
|
for (const label of ['隐藏字段', '系统字段', '审批结论', '审批附件']) await expect(drawer.getByLabel(label, { exact: true })).toHaveCount(0);
|
|
201
218
|
await drawer.getByLabel('申请名称', { exact: true }).fill('已更正的业务申请');
|
|
202
|
-
await drawer.getByRole('button', { name: '
|
|
219
|
+
await drawer.getByRole('button', { name: '保存' }).dblclick();
|
|
203
220
|
await expect(drawer.getByRole('button', { name: '关闭', exact: true })).toBeDisabled();
|
|
204
221
|
await page.keyboard.press('Escape');
|
|
205
222
|
await expect(drawer).toBeVisible();
|
|
206
|
-
const detail = page.getByRole('dialog', { name: '
|
|
207
|
-
await expect(detail.
|
|
223
|
+
const detail = page.getByRole('dialog', { name: '申请详情' });
|
|
224
|
+
await expect(detail.getByRole('heading', { name: '已更正的业务申请', exact: true })).toBeVisible();
|
|
208
225
|
await expect(detail.locator('input,textarea')).toHaveCount(0);
|
|
209
|
-
await expect(detail.getByText('已同意', { exact: true })).toBeVisible();
|
|
226
|
+
await expect(detail.locator('.oxa-record-detail-hero').getByText('已同意', { exact: true })).toBeVisible();
|
|
210
227
|
expect(state.correctionWrites).toHaveLength(1);
|
|
211
228
|
expect(state.correctionWrites[0].operations[0]).toMatchObject({ expectedRevision: 8, data: { title: '已更正的业务申请', amount: 28600 } });
|
|
212
229
|
expect(Object.keys(state.correctionWrites[0].operations[0].data).sort()).toEqual(['amount', 'title']);
|
|
@@ -221,11 +238,11 @@ test('an uncertain workflow edit preserves input and replays the original revisi
|
|
|
221
238
|
state.loseCorrectionResponse = true;
|
|
222
239
|
const drawer = await openCorrection(page);
|
|
223
240
|
await drawer.getByLabel('申请名称', { exact: true }).fill('网络重试后保留的申请');
|
|
224
|
-
await drawer.getByRole('button', { name: '
|
|
241
|
+
await drawer.getByRole('button', { name: '保存' }).click();
|
|
225
242
|
await expect(drawer.getByText('响应暂时不可用,请重试')).toBeVisible();
|
|
226
243
|
await expect(drawer.getByLabel('申请名称', { exact: true })).toHaveValue('网络重试后保留的申请');
|
|
227
|
-
await drawer.getByRole('button', { name: '
|
|
228
|
-
await expect(page.getByRole('dialog', { name: '
|
|
244
|
+
await drawer.getByRole('button', { name: '保存' }).click();
|
|
245
|
+
await expect(page.getByRole('dialog', { name: '申请详情' }).getByRole('heading', { name: '网络重试后保留的申请', exact: true })).toBeVisible();
|
|
229
246
|
expect(state.correctionWrites).toHaveLength(2);
|
|
230
247
|
expect(state.correctionWrites[1]).toEqual(state.correctionWrites[0]);
|
|
231
248
|
expect(state.correctionEffects).toBe(1);
|
|
@@ -238,9 +255,9 @@ test('CAS conflicts retain the original form and revision without a hidden reloa
|
|
|
238
255
|
const initialReads = state.correctionReads;
|
|
239
256
|
state.record.revision = 9;
|
|
240
257
|
await drawer.getByLabel('申请名称', { exact: true }).fill('尚未覆盖并发修改');
|
|
241
|
-
await drawer.getByRole('button', { name: '
|
|
258
|
+
await drawer.getByRole('button', { name: '保存' }).click();
|
|
242
259
|
await expect(drawer.getByText('数据已发生变化,请关闭后重新核对')).toBeVisible();
|
|
243
|
-
await drawer.getByRole('button', { name: '
|
|
260
|
+
await drawer.getByRole('button', { name: '保存' }).click();
|
|
244
261
|
await expect.poll(() => state.correctionWrites.length).toBe(2);
|
|
245
262
|
await expect(drawer.getByLabel('申请名称', { exact: true })).toHaveValue('尚未覆盖并发修改');
|
|
246
263
|
expect(state.correctionWrites[1]).toEqual(state.correctionWrites[0]);
|
|
@@ -255,7 +272,7 @@ test('revoked admin authority fails saving and hides editing when identity is re
|
|
|
255
272
|
const drawer = await openCorrection(page);
|
|
256
273
|
state.admin = false;
|
|
257
274
|
await drawer.getByLabel('申请名称', { exact: true }).fill('撤权后不能保存');
|
|
258
|
-
await drawer.getByRole('button', { name: '
|
|
275
|
+
await drawer.getByRole('button', { name: '保存' }).click();
|
|
259
276
|
await expect(drawer.getByText('当前用户无权编辑数据')).toBeVisible();
|
|
260
277
|
await expect(drawer.getByLabel('申请名称', { exact: true })).toHaveValue('撤权后不能保存');
|
|
261
278
|
await page.reload();
|
|
@@ -268,7 +285,7 @@ test('record edit load retries and rejects a Surface for another environment or
|
|
|
268
285
|
const state = await workflowEntryPlatform(page);
|
|
269
286
|
state.correctionReadFailure = true;
|
|
270
287
|
await page.goto('/workflow-entry.e2e.html');
|
|
271
|
-
await page.getByRole('button', { name: '编辑', exact: true }).click();
|
|
288
|
+
await page.getByRole('button', { name: '编辑', exact: true }).first().click();
|
|
272
289
|
const drawer = page.getByRole('dialog', { name: '编辑数据' });
|
|
273
290
|
await expect(drawer.getByText('暂时无法读取编辑表单')).toBeVisible();
|
|
274
291
|
state.correctionReadFailure = false;
|
|
@@ -309,8 +326,8 @@ test('workflow launch retries one command, blocks close while processing and ope
|
|
|
309
326
|
await page.keyboard.press('Escape');
|
|
310
327
|
await expect(drawer).toBeVisible();
|
|
311
328
|
state.processStatus = 'started';
|
|
312
|
-
const detail = page.getByRole('dialog', { name: '
|
|
313
|
-
await expect(detail.
|
|
329
|
+
const detail = page.getByRole('dialog', { name: '申请详情' });
|
|
330
|
+
await expect(detail.getByRole('heading', { name: '抽屉新增的申请', exact: true })).toBeVisible();
|
|
314
331
|
await expect(detail.locator('input,textarea')).toHaveCount(0);
|
|
315
332
|
expect(state.launches).toHaveLength(2);
|
|
316
333
|
expect(state.launches[1]).toEqual(state.launches[0]);
|
|
@@ -378,7 +395,10 @@ test('detail switches to the ordinary full-width editor and back with only one d
|
|
|
378
395
|
const state = await workflowEntryPlatform(page);
|
|
379
396
|
await page.goto('/workflow-entry.e2e.html');
|
|
380
397
|
await page.getByRole('button', { name: '查看', exact: true }).click();
|
|
381
|
-
const detail = page.getByRole('dialog', { name: '
|
|
398
|
+
const detail = page.getByRole('dialog', { name: '申请详情' });
|
|
399
|
+
await expect(detail.getByRole('heading', { name: '原业务申请', exact: true })).toBeVisible();
|
|
400
|
+
await expect(detail).toHaveCSS('width', '850px');
|
|
401
|
+
await page.screenshot({ path: testInfo.outputPath('workflow-detail-drawer-850.png'), animations: 'disabled' });
|
|
382
402
|
await detail.getByRole('button', { name: '全屏', exact: true }).click();
|
|
383
403
|
await expect.poll(async () => Math.round((await detail.boundingBox())!.width)).toBe(page.viewportSize()!.width);
|
|
384
404
|
await detail.getByRole('button', { name: '编辑', exact: true }).click();
|
|
@@ -390,28 +410,50 @@ test('detail switches to the ordinary full-width editor and back with only one d
|
|
|
390
410
|
await expect.poll(async () => Math.round((await editor.boundingBox())!.width)).toBe(page.viewportSize()!.width);
|
|
391
411
|
await editor.getByLabel('申请名称', { exact: true }).fill('直接编辑的数据');
|
|
392
412
|
await page.screenshot({ path: testInfo.outputPath('workflow-ordinary-editor-fullscreen.png'), animations: 'disabled' });
|
|
393
|
-
await editor.getByRole('button', { name: '
|
|
394
|
-
await expect(detail.
|
|
413
|
+
await editor.getByRole('button', { name: '保存', exact: true }).click();
|
|
414
|
+
await expect(detail.getByRole('heading', { name: '直接编辑的数据', exact: true })).toBeVisible();
|
|
395
415
|
await expect(page.getByRole('dialog')).toHaveCount(1);
|
|
396
416
|
await expect(detail.getByRole('button', { name: '退出全屏' })).toBeVisible();
|
|
397
417
|
expect(state.correctionWrites[0].operations[0].id).toBe('record-1');
|
|
398
418
|
expect(state.launches).toEqual([]);
|
|
399
419
|
});
|
|
400
420
|
|
|
421
|
+
test('standalone record detail keeps the approved content width and cancels editing in place', async ({ page }, testInfo) => {
|
|
422
|
+
const state = await workflowEntryPlatform(page);
|
|
423
|
+
await page.setViewportSize({ width: 1440, height: 900 });
|
|
424
|
+
await page.goto('/workflow-entry.e2e.html?mode=desktop-detail');
|
|
425
|
+
const heading = page.getByRole('heading', { name: '原业务申请', exact: true });
|
|
426
|
+
await expect(heading).toBeVisible();
|
|
427
|
+
await expect(page.getByRole('dialog')).toHaveCount(0);
|
|
428
|
+
await expect(page.locator('.oxa-record-detail-body > .oxa-record-detail-width')).toHaveCSS('width', '1056px');
|
|
429
|
+
await expect(page.locator('.oxa-record-detail-hero h1')).toHaveCSS('font-size', '23px');
|
|
430
|
+
await expect(page.getByRole('tab')).toHaveCount(3);
|
|
431
|
+
await page.screenshot({ path: testInfo.outputPath('workflow-detail-standalone-1056.png'), animations: 'disabled' });
|
|
432
|
+
await page.getByRole('button', { name: '编辑', exact: true }).first().click();
|
|
433
|
+
await page.getByLabel('申请名称', { exact: true }).fill('取消的更改');
|
|
434
|
+
await page.screenshot({ path: testInfo.outputPath('workflow-detail-standalone-inline-edit.png'), animations: 'disabled' });
|
|
435
|
+
await page.getByRole('button', { name: /取\s*消/ }).click();
|
|
436
|
+
await expect(heading).toBeVisible();
|
|
437
|
+
expect(state.correctionWrites).toHaveLength(0);
|
|
438
|
+
expect(state.launches).toHaveLength(0);
|
|
439
|
+
});
|
|
440
|
+
|
|
401
441
|
test('mobile detail edits on the current page and returns to the same readonly record', async ({ page }, testInfo) => {
|
|
402
442
|
const state = await workflowEntryPlatform(page);
|
|
403
443
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
404
444
|
await page.goto('/workflow-entry.e2e.html?mode=mobile-detail');
|
|
405
|
-
await expect(page.
|
|
406
|
-
await page.
|
|
445
|
+
await expect(page.getByRole('heading', { name: '原业务申请', exact: true })).toBeVisible();
|
|
446
|
+
await expect(page.locator('.oxa-record-detail-hero h1')).toHaveCSS('font-size', '20px');
|
|
447
|
+
await page.screenshot({ path: testInfo.outputPath('workflow-detail-mobile-390.png'), animations: 'disabled' });
|
|
448
|
+
await page.getByRole('button', { name: '编辑', exact: true }).first().click();
|
|
407
449
|
await expect(page.getByRole('dialog')).toHaveCount(0);
|
|
408
450
|
const form = page.locator('.oxa-mobile-form');
|
|
409
451
|
await expect(form.getByRole('textbox', { name: '申请名称', exact: true })).toHaveValue('原业务申请');
|
|
410
452
|
await form.getByRole('textbox', { name: '申请名称', exact: true }).fill('移动页面直接修改');
|
|
411
453
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
|
412
454
|
await page.screenshot({ path: testInfo.outputPath('workflow-ordinary-editor-mobile.png'), animations: 'disabled' });
|
|
413
|
-
await form.getByRole('button', { name: '
|
|
414
|
-
await expect(page.
|
|
455
|
+
await form.getByRole('button', { name: '保存', exact: true }).click();
|
|
456
|
+
await expect(page.getByRole('heading', { name: '移动页面直接修改', exact: true })).toBeVisible();
|
|
415
457
|
await expect(page.getByRole('dialog')).toHaveCount(0);
|
|
416
458
|
await expect(page.locator('.oxa-readonly-form input')).toHaveCount(0);
|
|
417
459
|
expect(state.record.id).toBe('record-1');
|
|
@@ -423,30 +465,27 @@ test('ordinary workflow editing reuses Native uploads, rich text and parent-chil
|
|
|
423
465
|
state.complexFields = true;
|
|
424
466
|
const editor = await openCorrection(page, '?fields=complex');
|
|
425
467
|
const subtable = editor.locator('.oxa-subtable-field');
|
|
426
|
-
await expect(subtable.
|
|
468
|
+
await expect(subtable.getByLabel('品名', { exact: true }).first()).toHaveValue('原明细甲');
|
|
427
469
|
await expect(subtable.getByText('仅详情说明', { exact: true })).toHaveCount(0);
|
|
428
470
|
await expect(subtable.getByText('隐藏明细字段', { exact: true })).toHaveCount(0);
|
|
429
471
|
await editor.locator('input[type="file"]').first().setInputFiles({ name: '申请说明.txt', mimeType: 'text/plain', buffer: Buffer.from('说明') });
|
|
430
472
|
await expect(editor.getByText('申请说明.txt', { exact: true })).toBeVisible();
|
|
431
473
|
await editor.getByRole('textbox', { name: '富文本内容' }).fill('直接修改富文本说明');
|
|
432
|
-
await subtable.
|
|
433
|
-
|
|
434
|
-
await expect(
|
|
435
|
-
|
|
436
|
-
await
|
|
437
|
-
await
|
|
438
|
-
await subtable.getByRole('
|
|
439
|
-
await subtable.
|
|
440
|
-
|
|
441
|
-
await newRow.getByLabel('品名', { exact: true }).fill('新增明细丙');
|
|
442
|
-
await newRow.getByLabel('数量', { exact: true }).fill('5');
|
|
443
|
-
await newRow.getByRole('button', { name: '确 定' }).click();
|
|
474
|
+
await subtable.getByLabel('品名', { exact: true }).first().fill('');
|
|
475
|
+
await editor.getByRole('button', { name: '保存', exact: true }).click();
|
|
476
|
+
await expect(editor.getByText('请完善子表单第 1 项')).toBeVisible();
|
|
477
|
+
expect(state.correctionWrites).toHaveLength(0);
|
|
478
|
+
await subtable.getByLabel('品名', { exact: true }).first().fill('修改后的明细甲');
|
|
479
|
+
await subtable.getByRole('row').nth(2).getByRole('button', { name: '删除', exact: true }).click();
|
|
480
|
+
await subtable.getByRole('button', { name: '新增一项' }).click();
|
|
481
|
+
await subtable.getByLabel('品名', { exact: true }).nth(1).fill('新增明细丙');
|
|
482
|
+
await subtable.getByLabel('数量', { exact: true }).nth(1).fill('5');
|
|
444
483
|
await expect(page.getByRole('dialog')).toHaveCount(1);
|
|
445
484
|
await page.screenshot({ path: testInfo.outputPath('workflow-ordinary-editor-attachments-subtable.png'), animations: 'disabled' });
|
|
446
|
-
await editor.getByRole('button', { name: '
|
|
447
|
-
await expect(page.getByRole('dialog', { name: '
|
|
448
|
-
await expect(page.getByRole('dialog', { name: '
|
|
449
|
-
await expect(page.getByRole('dialog', { name: '
|
|
485
|
+
await editor.getByRole('button', { name: '保存', exact: true }).click();
|
|
486
|
+
await expect(page.getByRole('dialog', { name: '申请详情' })).toBeVisible();
|
|
487
|
+
await expect(page.getByRole('dialog', { name: '申请详情' }).getByText('保留详情说明', { exact: true })).toBeVisible();
|
|
488
|
+
await expect(page.getByRole('dialog', { name: '申请详情' }).getByText('不得显示的明细值', { exact: true })).toHaveCount(0);
|
|
450
489
|
expect(state.uploads).toMatchObject([{ fieldCode: 'attachments', recordId: 'record-1' }]);
|
|
451
490
|
const operations = state.correctionWrites[0].operations;
|
|
452
491
|
expect(operations[0]).toMatchObject({ operation: 'update', id: 'record-1', expectedRevision: 8, data: { attachments: [{ id: 'file-1' }] } });
|
|
@@ -467,18 +506,18 @@ test('mobile child editing selects form fields while readonly details retain the
|
|
|
467
506
|
state.complexFields = true;
|
|
468
507
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
469
508
|
await page.goto('/workflow-entry.e2e.html?mode=mobile-detail&fields=complex');
|
|
470
|
-
await expect(page.
|
|
509
|
+
await expect(page.locator('.oxa-workflow-subtable-mobile')).toContainText('保留详情说明');
|
|
471
510
|
await expect(page.getByLabel('隐藏明细字段', { exact: true })).toHaveCount(0);
|
|
472
|
-
await page.getByRole('button', { name: '编辑', exact: true }).click();
|
|
511
|
+
await page.getByRole('button', { name: '编辑', exact: true }).first().click();
|
|
473
512
|
const form = page.locator('.oxa-mobile-form');
|
|
474
513
|
const childName = form.getByRole('textbox', { name: '品名', exact: true }).first();
|
|
475
514
|
await expect(childName).toHaveValue('原明细甲');
|
|
476
515
|
await expect(form.getByLabel('仅详情说明', { exact: true })).toHaveCount(0);
|
|
477
516
|
await expect(form.getByLabel('隐藏明细字段', { exact: true })).toHaveCount(0);
|
|
478
517
|
await childName.fill('移动明细修改');
|
|
479
|
-
await form.getByRole('button', { name: '
|
|
480
|
-
await expect(page.
|
|
481
|
-
await expect(page.
|
|
518
|
+
await form.getByRole('button', { name: '保存', exact: true }).click();
|
|
519
|
+
await expect(page.locator('.oxa-workflow-subtable-mobile')).toContainText('保留详情说明');
|
|
520
|
+
await expect(page.locator('.oxa-workflow-subtable-mobile')).toContainText('移动明细修改');
|
|
482
521
|
await expect(page.getByRole('dialog')).toHaveCount(0);
|
|
483
522
|
expect(state.correctionWrites[0].operations.slice(1)).toEqual([
|
|
484
523
|
{ operation: 'update', resourceCode: 'purchase-lines', id: 'line-1', expectedRevision: 3, data: { name: '移动明细修改' } },
|
|
@@ -526,7 +526,7 @@ function processCommandSurface() {
|
|
|
526
526
|
};
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
function todoPage(offset = 0) {
|
|
529
|
+
function todoPage(offset = 0, view = 'all') {
|
|
530
530
|
const first = {
|
|
531
531
|
messageId: '33333333-3333-4333-8333-333333333333',
|
|
532
532
|
recipientId: '44444444-4444-4444-8444-444444444444',
|
|
@@ -560,7 +560,7 @@ function todoPage(offset = 0) {
|
|
|
560
560
|
schemaVersion: 'openxiangda.application-todo-center/v2',
|
|
561
561
|
appCode,
|
|
562
562
|
environmentKey: 'preproduction',
|
|
563
|
-
view
|
|
563
|
+
view,
|
|
564
564
|
counts: { pending: 1, informational: 2, completed: 3, unread: 1 },
|
|
565
565
|
items: [offset > 0 ? second : first],
|
|
566
566
|
total: 2,
|
|
@@ -649,7 +649,7 @@ async function mockWorkflow(
|
|
|
649
649
|
return route.fulfill({
|
|
650
650
|
contentType: 'application/json',
|
|
651
651
|
body: JSON.stringify(
|
|
652
|
-
envelope(todoPage(Number(url.searchParams.get('offset') || 0))),
|
|
652
|
+
envelope(todoPage(Number(url.searchParams.get('offset') || 0), url.searchParams.get('view') || 'all')),
|
|
653
653
|
),
|
|
654
654
|
});
|
|
655
655
|
}
|
|
@@ -709,6 +709,7 @@ async function mockWorkflow(
|
|
|
709
709
|
});
|
|
710
710
|
}
|
|
711
711
|
if (path.endsWith('/workflow/work-center/items')) {
|
|
712
|
+
const view = url.searchParams.get('view') || 'pending';
|
|
712
713
|
return route.fulfill({
|
|
713
714
|
contentType: 'application/json',
|
|
714
715
|
body: JSON.stringify(
|
|
@@ -716,11 +717,12 @@ async function mockWorkflow(
|
|
|
716
717
|
engineVersion: '2.0',
|
|
717
718
|
authorizationMode: 'current_user_role_union',
|
|
718
719
|
authorizationDigest: 'digest-1',
|
|
719
|
-
|
|
720
|
+
view,
|
|
721
|
+
counts: { created: 1, pending: completed ? 0 : 1, handled: completed ? 1 : 0, cc: 1 },
|
|
720
722
|
total: 1,
|
|
721
723
|
limit: 20,
|
|
722
724
|
offset: 0,
|
|
723
|
-
items: [task(completed, options.customDetail)],
|
|
725
|
+
items: [{ ...task(completed, options.customDetail), id: view === 'pending' ? taskId : instanceId, itemKind: view === 'pending' ? 'task' : 'instance', instanceId, taskId: view === 'pending' ? taskId : null, workflowTitle: '采购审批', taskTitle: '负责人审批', instanceStatus: completed ? 'approved' : 'running', resourceCode: 'purchase-orders', recordId: 'purchase-1', occurredAt: '2026-08-25T01:00:00.000Z' }],
|
|
724
726
|
})
|
|
725
727
|
),
|
|
726
728
|
});
|
|
@@ -803,9 +805,55 @@ test('renders a paged desktop work center from the current-user role union', asy
|
|
|
803
805
|
}) => {
|
|
804
806
|
await mockWorkflow(page);
|
|
805
807
|
await page.goto('/workflow-experience.e2e.html?initial=/work-center');
|
|
806
|
-
await expect(page.getByRole('heading', { name: '
|
|
807
|
-
await expect(page.getByRole('
|
|
808
|
-
await expect(page.getByText('
|
|
808
|
+
await expect(page.getByRole('heading', { name: '待办中心' })).toBeVisible();
|
|
809
|
+
await expect(page.getByRole('button', { name: '采购申请审批' })).toBeVisible();
|
|
810
|
+
await expect(page.getByText('采购审批', { exact: true })).toBeVisible();
|
|
811
|
+
for (const [view, label] of [['created', '我创建的'], ['handled', '我已处理'], ['cc', '抄送我的'], ['pending', '待我审批']]) {
|
|
812
|
+
const request = page.waitForRequest(request => new URL(request.url()).pathname.endsWith('/workflow/work-center/items') && new URL(request.url()).searchParams.get('view') === view);
|
|
813
|
+
await page.getByRole('tab', { name: new RegExp(label) }).click();
|
|
814
|
+
await request;
|
|
815
|
+
await expect(page.getByRole('button', { name: '采购申请审批' })).toBeVisible();
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
test('a current task handler can send CC with the Surface instance command and multiple directory users', async ({ page }) => {
|
|
820
|
+
await mockWorkflow(page);
|
|
821
|
+
const current = detail();
|
|
822
|
+
const cc = {
|
|
823
|
+
key: 'cc', kind: 'workflow_command', label: '抄送', group: 'instance_control', audience: 'participant',
|
|
824
|
+
placement: 'overflow', emphasis: 'neutral', tone: 'neutral', visible: true, enabled: true,
|
|
825
|
+
inputSchema: { type: 'object', additionalProperties: false, required: ['userIds'], properties: {
|
|
826
|
+
userIds: { type: 'array', title: '抄送给', minItems: 1, maxItems: 20, uniqueItems: true, items: { type: 'string' } },
|
|
827
|
+
comment: { type: 'string', title: '留言', maxLength: 4000 },
|
|
828
|
+
} },
|
|
829
|
+
uiSchema: { presentation: 'dialog', confirmText: '确认抄送', properties: { userIds: { component: 'user_select', mode: 'multiple' } } },
|
|
830
|
+
execute: { method: 'POST', href: `/openxiangda-api/v2/applications/${appCode}/workflow/instances/${instanceId}/commands/cc`, idempotencyRequired: true },
|
|
831
|
+
refresh: ['surface', 'timeline', 'work_center'],
|
|
832
|
+
};
|
|
833
|
+
const augmented = { ...current, operations: [...current.operations, cc], surface: { ...current.surface, operations: [...current.surface.operations, cc] } };
|
|
834
|
+
await page.route('**/workflow/tasks/*/detail', route => route.fulfill({ json: envelope(augmented) }));
|
|
835
|
+
await page.route('**/directory/users?**', route => route.fulfill({ json: envelope({ items: [{ id: 'cc-user-1', label: '陈老师' }, { id: 'cc-user-2', label: '李老师' }], nextCursor: null }) }));
|
|
836
|
+
let writes = 0;
|
|
837
|
+
await page.route('**/workflow/instances/*/commands/cc', async route => {
|
|
838
|
+
writes += 1;
|
|
839
|
+
expect(route.request().postDataJSON()).toMatchObject({ commandToken: current.surface.commandToken, input: { userIds: ['cc-user-1', 'cc-user-2'], comment: '请查阅' } });
|
|
840
|
+
expect(route.request().postDataJSON().idempotencyKey).toBeTruthy();
|
|
841
|
+
expect(route.request().headers()['x-openxiangda-csrf-token']).toBe('workflow-csrf-1');
|
|
842
|
+
await route.fulfill({ json: envelope({ status: 'completed', advanced: false, instanceId }) });
|
|
843
|
+
});
|
|
844
|
+
await page.goto(workflowFixtureUrl(`/tasks/${taskId}`));
|
|
845
|
+
await page.getByRole('button', { name: '更多操作' }).click();
|
|
846
|
+
await page.getByRole('button', { name: '抄送', exact: true }).click();
|
|
847
|
+
await page.getByRole('button', { name: '确认抄送' }).click();
|
|
848
|
+
expect(writes).toBe(0);
|
|
849
|
+
const select = page.getByRole('dialog').getByRole('combobox');
|
|
850
|
+
await select.fill('老师');
|
|
851
|
+
await page.getByTitle('陈老师', { exact: true }).click();
|
|
852
|
+
await page.getByTitle('李老师', { exact: true }).click();
|
|
853
|
+
await page.getByLabel('留言').fill('请查阅');
|
|
854
|
+
await page.getByRole('button', { name: '确认抄送' }).click();
|
|
855
|
+
await expect.poll(() => writes).toBe(1);
|
|
856
|
+
await expect(page.getByRole('button', { name: /同\s*意/ })).toBeVisible();
|
|
809
857
|
});
|
|
810
858
|
|
|
811
859
|
test('renders the generic desktop and mobile application todo center without a second router', async ({
|
|
@@ -813,7 +861,7 @@ test('renders the generic desktop and mobile application todo center without a s
|
|
|
813
861
|
}) => {
|
|
814
862
|
await mockWorkflow(page);
|
|
815
863
|
await page.goto('/workflow-experience.e2e.html?initial=/todos');
|
|
816
|
-
await expect(page.getByRole('heading', { name: '
|
|
864
|
+
await expect(page.getByRole('heading', { name: '消息中心' })).toBeVisible();
|
|
817
865
|
await expect(page.getByTestId('standard-frame-desktop')).toHaveCount(0);
|
|
818
866
|
await expect(page.locator('.oxa-todo-table')).toBeVisible();
|
|
819
867
|
await expect(page.locator('.oxa-todo-preview-card')).toHaveCount(0);
|
|
@@ -881,7 +929,7 @@ test('selects paired application todo renderers while the platform owns data, pa
|
|
|
881
929
|
await page.getByRole('button', { name: '切换未读' }).click();
|
|
882
930
|
await unreadRequest;
|
|
883
931
|
await expect(page.getByTestId('custom-todo-query')).toContainText(
|
|
884
|
-
'
|
|
932
|
+
'all/true/all',
|
|
885
933
|
);
|
|
886
934
|
|
|
887
935
|
await page.getByRole('button', { name: '设备采购申请待审批' }).click();
|
|
@@ -970,7 +1018,7 @@ test('routes desktop and mobile work-center entries to the declared custom detai
|
|
|
970
1018
|
}) => {
|
|
971
1019
|
await mockWorkflow(page, { customDetail: true });
|
|
972
1020
|
await page.goto('/workflow-experience.e2e.html?initial=/work-center');
|
|
973
|
-
await page.getByRole('
|
|
1021
|
+
await page.getByRole('button', { name: '采购申请审批' }).click();
|
|
974
1022
|
await expect(page.getByTestId('custom-workflow-detail')).toContainText(
|
|
975
1023
|
`${instanceId} / ${taskId}`
|
|
976
1024
|
);
|
|
@@ -1008,7 +1056,7 @@ test('renders a standalone desktop detail on the canonical user URL', async ({
|
|
|
1008
1056
|
await page.goto(
|
|
1009
1057
|
workflowFixtureUrl(`/tasks/${taskId}?from=todo#approval`),
|
|
1010
1058
|
);
|
|
1011
|
-
await expect(page.locator('.oxa-
|
|
1059
|
+
await expect(page.locator('.oxa-record-detail-desktop.is-page')).toBeVisible();
|
|
1012
1060
|
await expect(page).toHaveURL(
|
|
1013
1061
|
new RegExp(`/tasks/${taskId}\\?from=todo#approval$`),
|
|
1014
1062
|
);
|
|
@@ -1019,6 +1067,7 @@ test('renders a standalone desktop detail on the canonical user URL', async ({
|
|
|
1019
1067
|
await expect(page.getByText('业务数据已变化')).toHaveCount(0);
|
|
1020
1068
|
await expect(page.getByText('流程信息')).toHaveCount(0);
|
|
1021
1069
|
await expect(page.getByText('操作记录')).toHaveCount(0);
|
|
1070
|
+
await page.getByRole('tab', { name: '审批历史' }).click();
|
|
1022
1071
|
await expect(page.getByText('由值班负责人继续处理')).toHaveCount(1);
|
|
1023
1072
|
await expect(page.getByRole('button', { name: /同\s*意/ })).toBeVisible();
|
|
1024
1073
|
await expect(page.getByRole('button', { name: /拒\s*绝/ })).toBeVisible();
|
|
@@ -1042,8 +1091,9 @@ test('renders the independent mobile task and executes only a Surface operation'
|
|
|
1042
1091
|
await mockWorkflow(page);
|
|
1043
1092
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
1044
1093
|
await page.goto(`/workflow-experience.e2e.html?initial=/m/tasks/${taskId}`);
|
|
1045
|
-
await expect(page.locator('.oxa-
|
|
1094
|
+
await expect(page.locator('.oxa-record-detail-mobile')).toBeVisible();
|
|
1046
1095
|
await expect(page.getByText('¥30,000')).toBeVisible();
|
|
1096
|
+
await page.getByRole('tab', { name: '审批历史' }).click();
|
|
1047
1097
|
await expect(page.getByText('财务负责人').first()).toBeVisible();
|
|
1048
1098
|
await expect(page.getByRole('button', { name: /同\s*意/ })).toBeVisible();
|
|
1049
1099
|
await page.getByRole('button', { name: /同\s*意/ }).click();
|
|
@@ -1064,7 +1114,7 @@ test('shows an explicit authorization error instead of an empty work center', as
|
|
|
1064
1114
|
await expect(page.getByRole('alert')).toContainText('403: forbidden', {
|
|
1065
1115
|
timeout: 30_000,
|
|
1066
1116
|
});
|
|
1067
|
-
await expect(page.getByRole('
|
|
1117
|
+
await expect(page.getByRole('button', { name: '采购申请审批' })).toHaveCount(0);
|
|
1068
1118
|
});
|
|
1069
1119
|
|
|
1070
1120
|
test('negotiates direct standard routes after BrowserRouter listener setup', async ({
|
|
@@ -1112,7 +1162,7 @@ test('negotiates direct standard routes after BrowserRouter listener setup', asy
|
|
|
1112
1162
|
await page.goto(
|
|
1113
1163
|
workflowFixtureUrl(`/tasks/${taskId}?from=todo#approval`),
|
|
1114
1164
|
);
|
|
1115
|
-
await expect(page.locator('.oxa-
|
|
1165
|
+
await expect(page.locator('.oxa-record-detail-mobile')).toBeVisible();
|
|
1116
1166
|
await expect(page).toHaveURL(
|
|
1117
1167
|
new RegExp(`/m/tasks/${taskId}\\?from=todo#approval$`),
|
|
1118
1168
|
);
|
|
@@ -1120,7 +1170,7 @@ test('negotiates direct standard routes after BrowserRouter listener setup', asy
|
|
|
1120
1170
|
await page.goto(
|
|
1121
1171
|
workflowFixtureUrl(`/workflows/${instanceId}?from=todo#timeline`),
|
|
1122
1172
|
);
|
|
1123
|
-
await expect(page.locator('.oxa-
|
|
1173
|
+
await expect(page.locator('.oxa-record-detail-mobile')).toBeVisible();
|
|
1124
1174
|
await expect(page).toHaveURL(
|
|
1125
1175
|
new RegExp(`/m/workflows/${instanceId}\\?from=todo#timeline$`),
|
|
1126
1176
|
);
|
|
@@ -1146,7 +1196,7 @@ test('negotiates launch pairs in both directions and preserves the mount basenam
|
|
|
1146
1196
|
);
|
|
1147
1197
|
|
|
1148
1198
|
await page.setViewportSize({ width: 1_440, height: 900 });
|
|
1149
|
-
await expect(page.locator('.oxa-workflow-
|
|
1199
|
+
await expect(page.locator('.oxa-workflow-entry')).toBeVisible();
|
|
1150
1200
|
await expect(page).toHaveURL(
|
|
1151
1201
|
new RegExp(
|
|
1152
1202
|
`${base}/workflows/purchase-approval/start\\?source=todo#launch$`,
|