mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 12:05:53 +00:00
Add SelectionList concept (#8054)
* Add SelectionList model, APIs and simple tests * Add managment entries * Add field to serializer * add more tests for parameters * Add support for SelectionList to CUI * Add selection option to PUI * fix display * add PUI admin entries * remove get_api_url * fix modeldict * Add models for meta * Add test for inactive lists * Add locking and testing for locking * ignore unneeded section * Add PUI testing for adding parameter * Add selectionList admin * also allow creating entries * extend tests * force click * and more testing * adapt test? * more assurance? * make test more robust * more retries but shorter runs * Update playwright.config.ts * Add docs * Add note regarding administration * Adapt to https://github.com/inventree/InvenTree/pull/8093 * make help text more descriptive * fix migration * remove unneeded UI entries * add lables and describtions to TableFields * factor out selectionList forms * add key to button * cleanup imports * add editable fields * Add function to add row * fix render warning * remove dead parameter * fix migrations * fix migrations * fix format * autofix * fix migrations * fix create / update loop * fix addition of empty lists * extend tests * adjust changelog entry * fix updating loop * update test name * merge migrations * simplify request * - Add entry count to list - Move parameter table to default accordion * fix test * fix test clearing section --------- Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
This commit is contained in:
@ -5,8 +5,8 @@ export default defineConfig({
|
||||
fullyParallel: true,
|
||||
timeout: 90000,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
workers: process.env.CI ? 2 : undefined,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 3 : undefined,
|
||||
reporter: process.env.CI ? [['html', { open: 'never' }], ['github']] : 'list',
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
|
@ -49,6 +49,7 @@ export type ApiFormAdjustFilterType = {
|
||||
* @param onValueChange : Callback function to call when the field value changes
|
||||
* @param adjustFilters : Callback function to adjust the filters for a related field before a query is made
|
||||
* @param adjustValue : Callback function to adjust the value of the field before it is sent to the API
|
||||
* @param addRow : Callback function to add a new row to a table field
|
||||
* @param onKeyDown : Callback function to get which key was pressed in the form to handle submission on enter
|
||||
*/
|
||||
export type ApiFormFieldType = {
|
||||
@ -94,6 +95,7 @@ export type ApiFormFieldType = {
|
||||
adjustValue?: (value: any) => any;
|
||||
onValueChange?: (value: any, record?: any) => void;
|
||||
adjustFilters?: (value: ApiFormAdjustFilterType) => any;
|
||||
addRow?: () => any;
|
||||
headers?: string[];
|
||||
depends_on?: string[];
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import { identifierString } from '../../../functions/conversion';
|
||||
import { InvenTreeIcon } from '../../../functions/icons';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { StandaloneField } from '../StandaloneField';
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
@ -109,6 +110,17 @@ export function TableField({
|
||||
field.onChange(val);
|
||||
};
|
||||
|
||||
const fieldDefinition = useMemo(() => {
|
||||
return {
|
||||
...definition,
|
||||
modelRenderer: undefined,
|
||||
onValueChange: undefined,
|
||||
adjustFilters: undefined,
|
||||
read_only: undefined,
|
||||
addRow: undefined
|
||||
};
|
||||
}, [definition]);
|
||||
|
||||
// Extract errors associated with the current row
|
||||
const rowErrors: any = useCallback(
|
||||
(idx: number) => {
|
||||
@ -134,6 +146,7 @@ export function TableField({
|
||||
})}
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
|
||||
<Table.Tbody>
|
||||
{value.length > 0 ? (
|
||||
value.map((item: any, idx: number) => {
|
||||
@ -170,6 +183,26 @@ export function TableField({
|
||||
</Table.Tr>
|
||||
)}
|
||||
</Table.Tbody>
|
||||
{definition.addRow && (
|
||||
<Table.Tfoot>
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={definition.headers?.length}>
|
||||
<AddItemButton
|
||||
tooltip={t`Add new row`}
|
||||
onClick={() => {
|
||||
if (definition.addRow === undefined) return;
|
||||
const ret = definition.addRow();
|
||||
if (ret) {
|
||||
const val = field.value;
|
||||
val.push(ret);
|
||||
field.onChange(val);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
</Table.Tfoot>
|
||||
)}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
@ -34,3 +34,16 @@ export function RenderImportSession({
|
||||
}): ReactNode {
|
||||
return instance && <RenderInlineModel primary={instance.data_file} />;
|
||||
}
|
||||
|
||||
export function RenderSelectionList({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return (
|
||||
instance && (
|
||||
<RenderInlineModel
|
||||
primary={instance.name}
|
||||
secondary={instance.description}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ import {
|
||||
RenderContentType,
|
||||
RenderError,
|
||||
RenderImportSession,
|
||||
RenderProjectCode
|
||||
RenderProjectCode,
|
||||
RenderSelectionList
|
||||
} from './Generic';
|
||||
import { ModelInformationDict } from './ModelType';
|
||||
import {
|
||||
@ -94,6 +95,7 @@ const RendererLookup: EnumDictionary<
|
||||
[ModelType.labeltemplate]: RenderLabelTemplate,
|
||||
[ModelType.pluginconfig]: RenderPlugin,
|
||||
[ModelType.contenttype]: RenderContentType,
|
||||
[ModelType.selectionlist]: RenderSelectionList,
|
||||
[ModelType.error]: RenderError
|
||||
};
|
||||
|
||||
|
@ -286,6 +286,12 @@ export const ModelInformationDict: ModelDict = {
|
||||
api_endpoint: ApiEndpoints.content_type_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
selectionlist: {
|
||||
label: () => t`Selection List`,
|
||||
label_multiple: () => t`Selection Lists`,
|
||||
api_endpoint: ApiEndpoints.selectionlist_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
error: {
|
||||
label: () => t`Error`,
|
||||
label_multiple: () => t`Errors`,
|
||||
|
@ -49,6 +49,8 @@ export enum ApiEndpoints {
|
||||
owner_list = 'user/owner/',
|
||||
content_type_list = 'contenttype/',
|
||||
icons = 'icons/',
|
||||
selectionlist_list = 'selection/',
|
||||
selectionlist_detail = 'selection/:id/',
|
||||
|
||||
// Barcode API endpoints
|
||||
barcode = 'barcode/',
|
||||
|
@ -33,5 +33,6 @@ export enum ModelType {
|
||||
labeltemplate = 'labeltemplate',
|
||||
pluginconfig = 'pluginconfig',
|
||||
contenttype = 'contenttype',
|
||||
selectionlist = 'selectionlist',
|
||||
error = 'error'
|
||||
}
|
||||
|
@ -2,7 +2,10 @@ import { t } from '@lingui/macro';
|
||||
import { IconPackages } from '@tabler/icons-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { api } from '../App';
|
||||
import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
||||
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
import { useGlobalSettingsState } from '../states/SettingsState';
|
||||
|
||||
/**
|
||||
@ -204,7 +207,7 @@ export function usePartParameterFields({
|
||||
setChoices(
|
||||
_choices.map((choice) => {
|
||||
return {
|
||||
label: choice.trim(),
|
||||
display_name: choice.trim(),
|
||||
value: choice.trim()
|
||||
};
|
||||
})
|
||||
@ -214,6 +217,22 @@ export function usePartParameterFields({
|
||||
setChoices([]);
|
||||
setFieldType('string');
|
||||
}
|
||||
} else if (record?.selectionlist) {
|
||||
api
|
||||
.get(
|
||||
apiUrl(ApiEndpoints.selectionlist_detail, record.selectionlist)
|
||||
)
|
||||
.then((res) => {
|
||||
setChoices(
|
||||
res.data.choices.map((item: any) => {
|
||||
return {
|
||||
value: item.value,
|
||||
display_name: item.label
|
||||
};
|
||||
})
|
||||
);
|
||||
setFieldType('choice');
|
||||
});
|
||||
} else {
|
||||
setChoices([]);
|
||||
setFieldType('string');
|
||||
|
117
src/frontend/src/forms/selectionListFields.tsx
Normal file
117
src/frontend/src/forms/selectionListFields.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Table } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import RemoveRowButton from '../components/buttons/RemoveRowButton';
|
||||
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||
import type {
|
||||
ApiFormFieldSet,
|
||||
ApiFormFieldType
|
||||
} from '../components/forms/fields/ApiFormField';
|
||||
import type { TableFieldRowProps } from '../components/forms/fields/TableField';
|
||||
|
||||
function BuildAllocateLineRow({
|
||||
props
|
||||
}: Readonly<{
|
||||
props: TableFieldRowProps;
|
||||
}>) {
|
||||
const valueField: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
field_type: 'string',
|
||||
name: 'value',
|
||||
required: true,
|
||||
value: props.item.value,
|
||||
onValueChange: (value: any) => {
|
||||
props.changeFn(props.idx, 'value', value);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
const labelField: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
field_type: 'string',
|
||||
name: 'label',
|
||||
required: true,
|
||||
value: props.item.label,
|
||||
onValueChange: (value: any) => {
|
||||
props.changeFn(props.idx, 'label', value);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
const descriptionField: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
field_type: 'string',
|
||||
name: 'description',
|
||||
required: true,
|
||||
value: props.item.description,
|
||||
onValueChange: (value: any) => {
|
||||
props.changeFn(props.idx, 'description', value);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
const activeField: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
field_type: 'boolean',
|
||||
name: 'active',
|
||||
required: true,
|
||||
value: props.item.active,
|
||||
onValueChange: (value: any) => {
|
||||
props.changeFn(props.idx, 'active', value);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
return (
|
||||
<Table.Tr key={`table-row-${props.item.pk}`}>
|
||||
<Table.Td>
|
||||
<StandaloneField fieldName='value' fieldDefinition={valueField} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<StandaloneField fieldName='label' fieldDefinition={labelField} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<StandaloneField
|
||||
fieldName='description'
|
||||
fieldDefinition={descriptionField}
|
||||
/>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<StandaloneField fieldName='active' fieldDefinition={activeField} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
);
|
||||
}
|
||||
|
||||
export function selectionListFields(): ApiFormFieldSet {
|
||||
return {
|
||||
name: {},
|
||||
description: {},
|
||||
active: {},
|
||||
locked: {},
|
||||
source_plugin: {},
|
||||
source_string: {},
|
||||
choices: {
|
||||
label: t`Entries`,
|
||||
description: t`List of entries to choose from`,
|
||||
field_type: 'table',
|
||||
value: [],
|
||||
headers: [t`Value`, t`Label`, t`Description`, t`Active`],
|
||||
modelRenderer: (row: TableFieldRowProps) => (
|
||||
<BuildAllocateLineRow props={row} />
|
||||
),
|
||||
addRow: () => {
|
||||
return {
|
||||
value: '',
|
||||
label: '',
|
||||
description: '',
|
||||
active: true
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -66,6 +66,8 @@ const MachineManagementPanel = Loadable(
|
||||
lazy(() => import('./MachineManagementPanel'))
|
||||
);
|
||||
|
||||
const PartParameterPanel = Loadable(lazy(() => import('./PartParameterPanel')));
|
||||
|
||||
const ErrorReportTable = Loadable(
|
||||
lazy(() => import('../../../../tables/settings/ErrorTable'))
|
||||
);
|
||||
@ -86,6 +88,10 @@ const CustomStateTable = Loadable(
|
||||
lazy(() => import('../../../../tables/settings/CustomStateTable'))
|
||||
);
|
||||
|
||||
const CustomUnitsTable = Loadable(
|
||||
lazy(() => import('../../../../tables/settings/CustomUnitsTable'))
|
||||
);
|
||||
|
||||
const PartParameterTemplateTable = Loadable(
|
||||
lazy(() => import('../../../../tables/part/PartParameterTemplateTable'))
|
||||
);
|
||||
@ -169,7 +175,7 @@ export default function AdminCenter() {
|
||||
name: 'part-parameters',
|
||||
label: t`Part Parameters`,
|
||||
icon: <IconList />,
|
||||
content: <PartParameterTemplateTable />
|
||||
content: <PartParameterPanel />
|
||||
},
|
||||
{
|
||||
name: 'category-parameters',
|
||||
|
@ -0,0 +1,29 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Accordion } from '@mantine/core';
|
||||
|
||||
import { StylishText } from '../../../../components/items/StylishText';
|
||||
import PartParameterTemplateTable from '../../../../tables/part/PartParameterTemplateTable';
|
||||
import SelectionListTable from '../../../../tables/part/SelectionListTable';
|
||||
|
||||
export default function PartParameterPanel() {
|
||||
return (
|
||||
<Accordion defaultValue='parametertemplate'>
|
||||
<Accordion.Item value='parametertemplate' key='parametertemplate'>
|
||||
<Accordion.Control>
|
||||
<StylishText size='lg'>{t`Part Parameter Template`}</StylishText>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<PartParameterTemplateTable />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value='selectionlist' key='selectionlist'>
|
||||
<Accordion.Control>
|
||||
<StylishText size='lg'>{t`Selection Lists`}</StylishText>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<SelectionListTable />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
@ -76,7 +76,8 @@ export default function PartParameterTemplateTable() {
|
||||
description: {},
|
||||
units: {},
|
||||
choices: {},
|
||||
checkbox: {}
|
||||
checkbox: {},
|
||||
selectionlist: {}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
134
src/frontend/src/tables/part/SelectionListTable.tsx
Normal file
134
src/frontend/src/tables/part/SelectionListTable.tsx
Normal file
@ -0,0 +1,134 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { selectionListFields } from '../../forms/selectionListFields';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import type { TableColumn } from '../Column';
|
||||
import { BooleanColumn } from '../ColumnRenderers';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { type RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
|
||||
/**
|
||||
* Table for displaying list of selectionlist items
|
||||
*/
|
||||
export default function SelectionListTable() {
|
||||
const table = useTable('selectionlist');
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const columns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessor: 'name',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'description',
|
||||
sortable: true
|
||||
},
|
||||
BooleanColumn({
|
||||
accessor: 'active'
|
||||
}),
|
||||
BooleanColumn({
|
||||
accessor: 'locked'
|
||||
}),
|
||||
{
|
||||
accessor: 'source_plugin',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'source_string',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'entry_count'
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
const newSelectionList = useCreateApiFormModal({
|
||||
url: ApiEndpoints.selectionlist_list,
|
||||
title: t`Add Selection List`,
|
||||
fields: selectionListFields(),
|
||||
table: table
|
||||
});
|
||||
|
||||
const [selectedSelectionList, setSelectedSelectionList] = useState<
|
||||
number | undefined
|
||||
>(undefined);
|
||||
|
||||
const editSelectionList = useEditApiFormModal({
|
||||
url: ApiEndpoints.selectionlist_list,
|
||||
pk: selectedSelectionList,
|
||||
title: t`Edit Selection List`,
|
||||
fields: selectionListFields(),
|
||||
table: table
|
||||
});
|
||||
|
||||
const deleteSelectionList = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.selectionlist_list,
|
||||
pk: selectedSelectionList,
|
||||
title: t`Delete Selection List`,
|
||||
table: table
|
||||
});
|
||||
|
||||
const rowActions = useCallback(
|
||||
(record: any): RowAction[] => {
|
||||
return [
|
||||
RowEditAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
setSelectedSelectionList(record.pk);
|
||||
editSelectionList.open();
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
hidden: !user.hasDeleteRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
setSelectedSelectionList(record.pk);
|
||||
deleteSelectionList.open();
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
return [
|
||||
<AddItemButton
|
||||
key='add-selection-list'
|
||||
onClick={() => newSelectionList.open()}
|
||||
tooltip={t`Add Selection List`}
|
||||
/>
|
||||
];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{newSelectionList.modal}
|
||||
{editSelectionList.modal}
|
||||
{deleteSelectionList.modal}
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.selectionlist_list)}
|
||||
tableState={table}
|
||||
columns={columns}
|
||||
props={{
|
||||
rowActions: rowActions,
|
||||
tableActions: tableActions,
|
||||
enableDownload: true
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
100
src/frontend/tests/settings/selectionList.spec.ts
Normal file
100
src/frontend/tests/settings/selectionList.spec.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { test } from '../baseFixtures';
|
||||
import { baseUrl } from '../defaults';
|
||||
import { doQuickLogin } from '../login';
|
||||
|
||||
test('PUI - Admin - Parameter', async ({ page }) => {
|
||||
await doQuickLogin(page, 'admin', 'inventree');
|
||||
await page.getByRole('button', { name: 'admin' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Admin Center' }).click();
|
||||
await page.getByRole('tab', { name: 'Part Parameters' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Selection Lists' }).click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// clean old data if exists
|
||||
await page
|
||||
.getByRole('cell', { name: 'some list' })
|
||||
.waitFor({ timeout: 200 })
|
||||
.then(async (cell) => {
|
||||
await page
|
||||
.getByRole('cell', { name: 'some list' })
|
||||
.locator('..')
|
||||
.getByLabel('row-action-menu-')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
// clean old data if exists
|
||||
await page.getByRole('button', { name: 'Part Parameter Template' }).click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page
|
||||
.getByRole('cell', { name: 'my custom parameter' })
|
||||
.waitFor({ timeout: 200 })
|
||||
.then(async (cell) => {
|
||||
await page
|
||||
.getByRole('cell', { name: 'my custom parameter' })
|
||||
.locator('..')
|
||||
.getByLabel('row-action-menu-')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
// Add selection list
|
||||
await page.getByRole('button', { name: 'Selection Lists' }).click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByLabel('action-button-add-selection-').waitFor();
|
||||
await page.getByLabel('action-button-add-selection-').click();
|
||||
await page.getByLabel('text-field-name').fill('some list');
|
||||
await page.getByLabel('text-field-description').fill('Listdescription');
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByRole('cell', { name: 'some list' }).waitFor();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Add parameter
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByRole('button', { name: 'Part Parameter Template' }).click();
|
||||
await page.getByLabel('action-button-add-parameter').waitFor();
|
||||
await page.getByLabel('action-button-add-parameter').click();
|
||||
await page.getByLabel('text-field-name').fill('my custom parameter');
|
||||
await page.getByLabel('text-field-description').fill('description');
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Search\.\.\.$/ })
|
||||
.nth(2)
|
||||
.click();
|
||||
await page
|
||||
.getByRole('option', { name: 'some list' })
|
||||
.locator('div')
|
||||
.first()
|
||||
.click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByRole('cell', { name: 'my custom parameter' }).click();
|
||||
|
||||
// Fill parameter
|
||||
await page.goto(`${baseUrl}/part/104/parameters/`);
|
||||
await page.getByLabel('Parameters').getByText('Parameters').waitFor();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByLabel('action-button-add-parameter').waitFor();
|
||||
await page.getByLabel('action-button-add-parameter').click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.getByText('New Part Parameter').waitFor();
|
||||
await page
|
||||
.getByText('Template *Parameter')
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Search\.\.\.$/ })
|
||||
.nth(2)
|
||||
.click();
|
||||
await page
|
||||
.getByText('Template *Parameter')
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Search\.\.\.$/ })
|
||||
.locator('input')
|
||||
.fill('my custom parameter');
|
||||
await page.getByRole('option', { name: 'my custom parameter' }).click();
|
||||
await page.getByLabel('choice-field-data').fill('2');
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
});
|
Reference in New Issue
Block a user