From 99f88ee09fa338ca904f08f83d25e9dcd65403fb Mon Sep 17 00:00:00 2001 From: hongboji <116709317+hongboji@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:31:27 +0800 Subject: [PATCH] test: approvals workflow e2e (#4781) --- .../src/e2e/e2ePageObjectModel.ts | 96 +++++++++++++++++-- .../plugin-workflow-test/src/e2e/e2eUtils.ts | 73 +++++++++++++- 2 files changed, 156 insertions(+), 13 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts index e0965c102..2442ad46d 100644 --- a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts +++ b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts @@ -97,14 +97,15 @@ export class ApprovalTriggerNode { configureFieldsButton: Locator; configureActionsButton: Locator; saveDraftSwitch: Locator; + preloadAssociationsDropDown: Locator; submitButton: Locator; cancelButton: Locator; addNodeButton: Locator; constructor(page: Page, triggerName: string, collectionName: string) { this.page = page; - this.node = page.getByText('TriggeraConfigure'); - this.nodeTitle = page.locator('textarea').filter({ hasText: triggerName }); - this.nodeConfigure = page.getByRole('button', { name: 'Configure' }); + this.node = page.getByLabel(`Trigger-${triggerName}`); + this.nodeTitle = page.getByLabel(`Trigger-${triggerName}`).getByRole('textbox'); + this.nodeConfigure = page.getByLabel(`Trigger-${triggerName}`).getByRole('button', { name: 'Configure' }); this.collectionDropDown = page .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') .locator('.ant-select-selection-search-input'); @@ -117,10 +118,9 @@ export class ApprovalTriggerNode { this.addBlockButton = page.getByLabel(`schema-initializer-Grid-ApprovalApplyAddBlockButton-${collectionName}`); this.addApplyFormMenu = page.getByRole('menuitem', { name: 'Apply form' }); this.configureFieldsButton = page.getByLabel(`schema-initializer-Grid-form:configureFields-${collectionName}`); - this.configureActionsButton = page.getByLabel( - `schema-initializer-ActionBar-ApprovalApplyAddActionButton-${collectionName}`, - ); + this.configureActionsButton = page.getByLabel(`schema-initializer-ActionBar-ApprovalApplyAddActionButton-${collectionName}`); this.saveDraftSwitch = page.getByRole('menuitem', { name: 'Save draft' }).getByRole('switch'); + this.preloadAssociationsDropDown = page.getByTestId('select-field-Preload associations'); this.submitButton = page.getByLabel('action-Action-Submit-workflows'); this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); this.addNodeButton = this.addNodeButton = page.getByLabel('add-button', { exact: true }); @@ -133,10 +133,13 @@ export class ApprovalPassthroughModeNode { nodeTitle: Locator; nodeConfigure: Locator; addAssigneesButton: Locator; + addSelectAssigneesMenu: Locator; + addQueryAssigneesMenu: Locator; assigneesDropDown: Locator; OrRadio: Locator; AndRadio: Locator; votingRadio: Locator; + votingThresholdEditBox: Locator; parallellyRadio: Locator; sequentiallyRadio: Locator; goToconfigureButton: Locator; @@ -161,18 +164,19 @@ export class ApprovalPassthroughModeNode { .getByLabel(`Approval-${nodeName}`, { exact: true }) .getByRole('button', { name: 'Configure' }); this.addAssigneesButton = page.getByRole('button', { name: 'plus Add assignee' }); + this.addSelectAssigneesMenu = page.getByRole('button', { name: 'Select assignees' }); + this.addQueryAssigneesMenu = page.getByRole('button', { name: 'Query assignees' }); this.assigneesDropDown = page.getByTestId('select-single'); this.OrRadio = page.getByLabel('Or', { exact: true }); this.AndRadio = page.getByLabel('And', { exact: true }); this.votingRadio = page.getByLabel('Voting', { exact: true }); + this.votingThresholdEditBox = page.getByLabel('block-item-NegotiationConfig-workflows-Negotiation mode').getByRole('spinbutton'); this.parallellyRadio = page.getByLabel('Parallelly', { exact: true }); this.sequentiallyRadio = page.getByLabel('Sequentially', { exact: true }); this.goToconfigureButton = page.getByRole('button', { name: 'Go to configure' }); this.addBlockButton = page.getByLabel('schema-initializer-Grid-ApprovalProcessAddBlockButton-workflows'); this.addDetailsMenu = page.getByRole('menuitem', { name: 'Details' }); - this.detailsConfigureFieldsButton = page.getByLabel( - `schema-initializer-Grid-ReadPrettyFormItemInitializers-${collectionName}`, - ); + this.detailsConfigureFieldsButton = page.getByLabel(`schema-initializer-Grid-details:configureFields-${collectionName}`); this.addActionsMenu = page.getByRole('menuitem', { name: 'Actions' }).getByRole('switch'); this.actionsConfigureFieldsButton = page.getByLabel('schema-initializer-Grid-FormItemInitializers-approvalRecords'); this.actionsConfigureActionsButton = page.getByLabel( @@ -188,6 +192,79 @@ export class ApprovalPassthroughModeNode { } } +export class ApprovalBranchModeNode { + readonly page: Page; + node: Locator; + nodeTitle: Locator; + nodeConfigure: Locator; + addAssigneesButton: Locator; + addSelectAssigneesMenu: Locator; + addQueryAssigneesMenu: Locator; + assigneesDropDown: Locator; + OrRadio: Locator; + AndRadio: Locator; + votingRadio: Locator; + votingThresholdEditBox: Locator; + parallellyRadio: Locator; + sequentiallyRadio: Locator; + goToconfigureButton: Locator; + addBlockButton: Locator; + addDetailsMenu: Locator; + detailsConfigureFieldsButton: Locator; + addActionsMenu: Locator; + actionsConfigureFieldsButton: Locator; + actionsConfigureActionsButton: Locator; + addApproveButton: Locator; + addRejectButton: Locator; + addReturnButton: Locator; + addNodeResult: Locator; + submitButton: Locator; + cancelButton: Locator; + addNodeButton: Locator; + addReturnBranchNodeButton: Locator; + addRejectBranchNodeButton: Locator; + addApproveBranchNodeButton: Locator; + endOnRejectCheckbox: Locator; + constructor(page: Page, nodeName: string, collectionName: string) { + this.page = page; + this.node = page.getByLabel(`Approval-${nodeName}`, { exact: true }); + this.nodeTitle = page.getByLabel(`Approval-${nodeName}`, { exact: true }).getByRole('textbox'); + this.nodeConfigure = page + .getByLabel(`Approval-${nodeName}`, { exact: true }) + .getByRole('button', { name: 'Configure' }); + this.addAssigneesButton = page.getByRole('button', { name: 'plus Add assignee' }); + this.addSelectAssigneesMenu = page.getByRole('button', { name: 'Select assignees' }); + this.addQueryAssigneesMenu = page.getByRole('button', { name: 'Query assignees' }); + this.assigneesDropDown = page.getByTestId('select-single'); + this.OrRadio = page.getByLabel('Or', { exact: true }); + this.AndRadio = page.getByLabel('And', { exact: true }); + this.votingRadio = page.getByLabel('Voting', { exact: true }); + this.votingThresholdEditBox = page.getByLabel('block-item-NegotiationConfig-workflows-Negotiation mode').getByRole('spinbutton'); + this.parallellyRadio = page.getByLabel('Parallelly', { exact: true }); + this.sequentiallyRadio = page.getByLabel('Sequentially', { exact: true }); + this.goToconfigureButton = page.getByRole('button', { name: 'Go to configure' }); + this.addBlockButton = page.getByLabel('schema-initializer-Grid-ApprovalProcessAddBlockButton-workflows'); + this.addDetailsMenu = page.getByRole('menuitem', { name: 'Details' }); + this.detailsConfigureFieldsButton = page.getByLabel(`schema-initializer-Grid-details:configureFields-${collectionName}`); + this.addActionsMenu = page.getByRole('menuitem', { name: 'Actions' }).getByRole('switch'); + this.actionsConfigureFieldsButton = page.getByLabel('schema-initializer-Grid-FormItemInitializers-approvalRecords'); + this.actionsConfigureActionsButton = page.getByLabel( + 'schema-initializer-ActionBar-ApprovalProcessAddActionButton-approvalRecords', + ); + this.addApproveButton = page.getByRole('menuitem', { name: 'Approve' }).getByRole('switch'); + this.addRejectButton = page.getByRole('menuitem', { name: 'Reject' }).getByRole('switch'); + this.addReturnButton = page.getByRole('menuitem', { name: 'Return' }).getByRole('switch'); + this.addNodeResult = page.getByRole('menuitem', { name: 'Node result right' }); + this.submitButton = page.getByLabel('action-Action-Submit-workflows'); + this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); + this.addNodeButton = page.getByLabel(`add-button-calculation-${nodeName}`, { exact: true }); + this.addReturnBranchNodeButton = page.getByLabel(`add-button-approval-${nodeName}-1`); + this.addApproveBranchNodeButton = page.getByLabel(`add-button-approval-${nodeName}-2`); + this.addRejectBranchNodeButton = page.getByLabel(`add-button-approval-${nodeName}--1`); + this.endOnRejectCheckbox = page.getByLabel('End the workflow after'); + } +} + export class ScheduleTriggerNode { readonly page: Page; node: Locator; @@ -656,4 +733,5 @@ export default module.exports = { ConditionBranchNode, SQLNode, ParallelBranchNode, + ApprovalBranchModeNode }; diff --git a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2eUtils.ts b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2eUtils.ts index 1cda17fc9..5c435c417 100644 --- a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2eUtils.ts +++ b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2eUtils.ts @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { request, Page } from '@nocobase/test/e2e'; +import { request, Browser } from '@nocobase/test/e2e'; const PORT = process.env.APP_PORT || 20000; const APP_BASE_URL = process.env.APP_BASE_URL || `http://localhost:${PORT}`; @@ -881,6 +881,68 @@ export const apiApplyApprovalEvent = async (data: any) => { return (await result.json()).data; }; +// 新增字段 +export const apiCreateField = async (collectionName: string, data: any) => { + const api = await request.newContext({ + storageState: process.env.PLAYWRIGHT_AUTH_FILE, + }); + const state = await api.storageState(); + const headers = getHeaders(state); + /* + { + "sourceKey": "id", + "foreignKey": "orgid", + "onDelete": "SET NULL", + "name": "dept", + "type": "hasMany", + "uiSchema": { + "x-component": "AssociationField", + "x-component-props": { + "multiple": true + }, + "title": "dept" + }, + "interface": "o2m", + "target": "tt_mnt_dept", + "targetKey": "id" + } + */ + const result = await api.post(`/api/collections/${collectionName}/fields:create`, { + headers, + data, + }); + + if (!result.ok()) { + throw new Error(await result.text()); + } + return (await result.json()).data; +}; +/* +{ + "data": { + "key": "np4llsa0fsx", + "name": "dept1", + "type": "hasMany", + "interface": "o2m", + "collectionName": "tt_mnt_org", + "description": null, + "parentKey": null, + "reverseKey": null, + "sourceKey": "id", + "foreignKey": "orgid", + "onDelete": "SET NULL", + "uiSchema": { + "x-component": "AssociationField", + "x-component-props": { + "multiple": true + }, + "title": "dept1" + }, + "target": "tt_mnt_dept", + "targetKey": "id" + } +} + */ const getStorageItem = (key: string, storageState: any) => { return storageState.origins .find((item) => item.origin === APP_BASE_URL) @@ -927,13 +989,15 @@ function getHeaders(storageState: any) { } // 用户登录新会话 -export const userLogin = async (page: Page, approvalUserEmail: string, approvalUser: string) => { - await page.goto(`${process.env.APP_BASE_URL}/signin`); +export const userLogin = async (browser: Browser, approvalUserEmail: string, approvalUser: string) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto('signin'); await page.getByPlaceholder('Email').fill(approvalUserEmail); await page.getByPlaceholder('Password').fill(approvalUser); await page.getByRole('button', { name: 'Sign in' }).click(); await page.waitForLoadState('networkidle'); - return page; + return context; }; export default module.exports = { @@ -956,4 +1020,5 @@ export default module.exports = { apiCreateRecordTriggerActionEvent, apiApplyApprovalEvent, userLogin, + apiCreateField };