2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

UI cleanup (#9140)

* Refactor SecurityContext page:

- Add Accordion to separate different groups
- Fix "make primary" button (requires PATCH)
- Responsive grid design

* Add splash screen background to more pages

* Adds playwright testing for email setup

* Refactoring

* Fix playwright tests
This commit is contained in:
Oliver 2025-02-22 17:13:12 +11:00 committed by GitHub
parent 44cca7ddf2
commit e447e4037b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 517 additions and 430 deletions

View File

@ -0,0 +1,24 @@
import { BackgroundImage } from '@mantine/core';
import { generateUrl } from '../functions/urls';
import { useServerApiState } from '../states/ApiState';
/**
* Render content within a "splash screen" container.
*/
export default function SplashScreen({
children
}: {
children: React.ReactNode;
}) {
const [server] = useServerApiState((state) => [state.server]);
if (server.customize?.splash) {
return (
<BackgroundImage src={generateUrl(server.customize.splash)}>
{children}
</BackgroundImage>
);
} else {
return <>{children}</>;
}
}

View File

@ -342,7 +342,7 @@ export async function ProviderLogin(
export function authApi( export function authApi(
url: string, url: string,
config: AxiosRequestConfig | undefined = undefined, config: AxiosRequestConfig | undefined = undefined,
method: 'get' | 'post' | 'put' | 'delete' = 'get', method: 'get' | 'patch' | 'post' | 'put' | 'delete' = 'get',
data?: any data?: any
) { ) {
const requestConfig = config || {}; const requestConfig = config || {};

View File

@ -15,6 +15,7 @@ import { notifications } from '@mantine/notifications';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { api } from '../../App'; import { api } from '../../App';
import SplashScreen from '../../components/SplashScreen';
import { StylishText } from '../../components/items/StylishText'; import { StylishText } from '../../components/items/StylishText';
import { ProtectedRoute } from '../../components/nav/Layout'; import { ProtectedRoute } from '../../components/nav/Layout';
import { LanguageContext } from '../../contexts/LanguageContext'; import { LanguageContext } from '../../contexts/LanguageContext';
@ -104,15 +105,17 @@ export default function Set_Password() {
return ( return (
<LanguageContext> <LanguageContext>
<ProtectedRoute> <ProtectedRoute>
<SplashScreen>
<Center mih='100vh'> <Center mih='100vh'>
<Container w='md' miw={425}> <Container w='md' miw={425}>
<Paper p='xl' withBorder>
<Stack> <Stack>
<StylishText size='xl'>{t`Reset Password`}</StylishText> <StylishText size='xl'>{t`Reset Password`}</StylishText>
<Divider /> <Divider />
{user.username() && ( {user.username() && (
<Paper> <Paper>
<Group> <Group>
<StylishText size='md'>{t`User`}</StylishText> <StylishText size='md'>{t`Username`}</StylishText>
<Text>{user.username()}</Text> <Text>{user.username()}</Text>
</Group> </Group>
</Paper> </Paper>
@ -145,8 +148,10 @@ export default function Set_Password() {
<Trans>Confirm</Trans> <Trans>Confirm</Trans>
</Button> </Button>
</Stack> </Stack>
</Paper>
</Container> </Container>
</Center> </Center>
</SplashScreen>
</ProtectedRoute> </ProtectedRoute>
</LanguageContext> </LanguageContext>
); );

View File

@ -1,16 +1,10 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import { Center, Container, Divider, Paper, Text } from '@mantine/core';
BackgroundImage,
Center,
Container,
Divider,
Paper,
Text
} from '@mantine/core';
import { useDisclosure, useToggle } from '@mantine/hooks'; import { useDisclosure, useToggle } from '@mantine/hooks';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { setApiDefaults } from '../../App'; import { setApiDefaults } from '../../App';
import SplashScreen from '../../components/SplashScreen';
import { AuthFormOptions } from '../../components/forms/AuthFormOptions'; import { AuthFormOptions } from '../../components/forms/AuthFormOptions';
import { import {
AuthenticationForm, AuthenticationForm,
@ -25,7 +19,6 @@ import {
doBasicLogin, doBasicLogin,
followRedirect followRedirect
} from '../../functions/auth'; } from '../../functions/auth';
import { generateUrl } from '../../functions/urls';
import { useServerApiState } from '../../states/ApiState'; import { useServerApiState } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState'; import { useLocalState } from '../../states/LocalState';
@ -73,16 +66,6 @@ export default function Login() {
return null; return null;
}, [server.customize]); }, [server.customize]);
const SplashComponent = useMemo(() => {
const temp = server.customize?.splash;
if (temp) {
return ({ children }: { children: React.ReactNode }) => (
<BackgroundImage src={generateUrl(temp)}>{children}</BackgroundImage>
);
}
return ({ children }: { children: React.ReactNode }) => <>{children}</>;
}, [server.customize]);
// Data manipulation functions // Data manipulation functions
function ChangeHost(newHost: string | null): void { function ChangeHost(newHost: string | null): void {
if (newHost === null) return; if (newHost === null) return;
@ -120,7 +103,7 @@ export default function Login() {
// Main rendering block // Main rendering block
return ( return (
<SplashComponent> <SplashScreen>
<Center mih='100vh'> <Center mih='100vh'>
<div <div
style={{ style={{
@ -159,6 +142,6 @@ export default function Login() {
</Container> </Container>
</div> </div>
</Center> </Center>
</SplashComponent> </SplashScreen>
); );
} }

View File

@ -3,14 +3,16 @@ import {
Button, Button,
Center, Center,
Container, Container,
Paper,
Stack, Stack,
TextInput, TextInput
Title
} from '@mantine/core'; } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { useState } from 'react'; import { useState } from 'react';
import SplashScreen from '../../components/SplashScreen';
import { StylishText } from '../../components/items/StylishText';
import { LanguageContext } from '../../contexts/LanguageContext'; import { LanguageContext } from '../../contexts/LanguageContext';
import { handleMfaLogin } from '../../functions/auth'; import { handleMfaLogin } from '../../functions/auth';
@ -22,12 +24,12 @@ export default function MFALogin() {
return ( return (
<LanguageContext> <LanguageContext>
<SplashScreen>
<Center mih='100vh'> <Center mih='100vh'>
<Container w='md' miw={425}> <Container w='md' miw={425}>
<Paper p='xl' withBorder>
<Stack> <Stack>
<Title> <StylishText size='xl'>{t`Multi-Factor Login`}</StylishText>
<Trans>MFA Login</Trans>
</Title>
<Stack> <Stack>
<TextInput <TextInput
required required
@ -49,11 +51,13 @@ export default function MFALogin() {
) )
} }
> >
<Trans>Log in</Trans> <Trans>Log In</Trans>
</Button> </Button>
</Stack> </Stack>
</Paper>
</Container> </Container>
</Center> </Center>
</SplashScreen>
</LanguageContext> </LanguageContext>
); );
} }

View File

@ -1,5 +1,6 @@
import { Trans, t } from '@lingui/macro'; import { Trans, t } from '@lingui/macro';
import { import {
Accordion,
Alert, Alert,
Badge, Badge,
Button, Button,
@ -9,11 +10,13 @@ import {
Loader, Loader,
Modal, Modal,
Radio, Radio,
SimpleGrid,
Stack, Stack,
Table, Table,
Text, Text,
TextInput, TextInput,
Title Title,
Tooltip
} from '@mantine/core'; } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { hideNotification, showNotification } from '@mantine/notifications'; import { hideNotification, showNotification } from '@mantine/notifications';
@ -43,13 +46,20 @@ export function SecurityContent() {
return ( return (
<Stack> <Stack>
<Title order={5}> <Accordion multiple defaultValue={['email', 'sso', 'mfa', 'token']}>
<Trans>Email Addresses</Trans> <Accordion.Item value='email'>
</Title> <Accordion.Control>
<StylishText size='lg'>{t`Email Addresses`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<EmailSection /> <EmailSection />
<Title order={5}> </Accordion.Panel>
<Trans>Single Sign On</Trans> </Accordion.Item>
</Title> <Accordion.Item value='sso'>
<Accordion.Control>
<StylishText size='lg'>{t`Single Sign On`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
{sso_enabled() ? ( {sso_enabled() ? (
<ProviderSection auth_config={auth_config} /> <ProviderSection auth_config={auth_config} />
) : ( ) : (
@ -61,62 +71,79 @@ export function SecurityContent() {
<Trans>Single Sign On is not enabled for this server </Trans> <Trans>Single Sign On is not enabled for this server </Trans>
</Alert> </Alert>
)} )}
<Title order={5}> </Accordion.Panel>
<Trans>Multifactor authentication</Trans> </Accordion.Item>
</Title> <Accordion.Item value='mfa'>
<Accordion.Control>
<StylishText size='lg'>{t`Multi-Factor Authentication`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<MfaSection /> <MfaSection />
<Title order={5}> </Accordion.Panel>
<Trans>Access Tokens</Trans> </Accordion.Item>
</Title> <Accordion.Item value='token'>
<Accordion.Control>
<StylishText size='lg'>{t`Access Tokens`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<TokenSection /> <TokenSection />
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</Stack> </Stack>
); );
} }
function EmailSection() { function EmailSection() {
const [value, setValue] = useState<string>(''); const [selectedEmail, setSelectedEmail] = useState<string>('');
const [newEmailValue, setNewEmailValue] = useState(''); const [newEmailValue, setNewEmailValue] = useState('');
const { isLoading, data, refetch } = useQuery({ const { isLoading, data, refetch } = useQuery({
queryKey: ['emails'], queryKey: ['emails'],
queryFn: () => queryFn: () =>
authApi(apiUrl(ApiEndpoints.auth_email)).then((res) => res.data.data) authApi(apiUrl(ApiEndpoints.auth_email)).then((res) => res.data.data)
}); });
const emailAvailable = useMemo(() => { const emailAvailable = useMemo(() => {
return data == undefined || data.length == 0; return data == undefined || data.length == 0;
}, [data]); }, [data]);
function runServerAction( function runServerAction(
action: 'post' | 'put' | 'delete' = 'post', action: 'patch' | 'post' | 'put' | 'delete' = 'post',
data?: any data?: any
) { ) {
const vals: any = data || { email: value }; const vals: any = data || { email: selectedEmail };
return authApi( return authApi(apiUrl(ApiEndpoints.auth_email), undefined, action, vals)
apiUrl(ApiEndpoints.auth_email), .then(() => {
undefined,
action,
vals
).then(() => {
refetch(); refetch();
})
.catch((err) => {
hideNotification('email-error');
showNotification({
id: 'email-error',
title: t`Error`,
message: t`Error while updating email`,
color: 'red'
});
}); });
} }
if (isLoading) return <Loader />; if (isLoading) return <Loader />;
return ( return (
<Grid> <SimpleGrid cols={{ xs: 1, md: 2 }} spacing='sm'>
<Grid.Col span={6}>
{emailAvailable ? ( {emailAvailable ? (
<Alert <Alert
icon={<IconAlertCircle size='1rem' />} icon={<IconAlertCircle size='1rem' />}
title={t`Not configured`} title={t`Not Configured`}
color='yellow' color='yellow'
> >
<Trans>Currently no email addresses are registered.</Trans> <Trans>Currently no email addresses are registered.</Trans>
</Alert> </Alert>
) : ( ) : (
<Radio.Group <Radio.Group
value={value} value={selectedEmail}
onChange={setValue} onChange={setSelectedEmail}
name='email_accounts' name='email_accounts'
label={t`The following email addresses are associated with your account:`} label={t`The following email addresses are associated with your account:`}
> >
@ -126,8 +153,9 @@ function EmailSection() {
key={email.email} key={email.email}
value={String(email.email)} value={String(email.email)}
label={ label={
<Group justify='space-between'> <Group justify='space-apart'>
{email.email} {email.email}
<Group justify='right'>
{email.primary && ( {email.primary && (
<Badge color='blue'> <Badge color='blue'>
<Trans>Primary</Trans> <Trans>Primary</Trans>
@ -143,53 +171,51 @@ function EmailSection() {
</Badge> </Badge>
)} )}
</Group> </Group>
</Group>
} }
/> />
))} ))}
</Stack>
</Radio.Group>
)}
</Grid.Col>
<Grid.Col span={6}>
<Stack>
<Text>
<Trans>Add Email Address</Trans>
</Text>
<TextInput
label={t`E-Mail`}
placeholder={t`E-Mail address`}
leftSection={<IconAt />}
value={newEmailValue}
onChange={(event) => setNewEmailValue(event.currentTarget.value)}
/>
</Stack>
</Grid.Col>
<Grid.Col span={6}>
<Group> <Group>
<Button <Button
onClick={() => onClick={() =>
runServerAction('post', { email: value, primary: true }) runServerAction('patch', {
email: selectedEmail,
primary: true
})
} }
disabled={emailAvailable} disabled={!selectedEmail}
> >
<Trans>Make Primary</Trans> <Trans>Make Primary</Trans>
</Button> </Button>
<Button <Button
onClick={() => runServerAction('put')} onClick={() => runServerAction('put')}
disabled={emailAvailable} disabled={!selectedEmail}
> >
<Trans>Re-send Verification</Trans> <Trans>Re-send Verification</Trans>
</Button> </Button>
<Button <Button
onClick={() => runServerAction('delete')} onClick={() => runServerAction('delete')}
disabled={emailAvailable} disabled={!selectedEmail}
color='red'
> >
<Trans>Remove</Trans> <Trans>Remove</Trans>
</Button> </Button>
</Group> </Group>
</Grid.Col> </Stack>
<Grid.Col span={6}> </Radio.Group>
)}
<Stack>
<StylishText size='md'>{t`Add Email Address`}</StylishText>
<TextInput
label={t`E-Mail`}
placeholder={t`E-Mail address`}
leftSection={<IconAt />}
aria-label='email-address-input'
value={newEmailValue}
onChange={(event) => setNewEmailValue(event.currentTarget.value)}
/>
<Button <Button
aria-label='email-address-submit'
onClick={() => onClick={() =>
runServerAction('post', { email: newEmailValue }).catch((err) => { runServerAction('post', { email: newEmailValue }).catch((err) => {
if (err.status == 400) { if (err.status == 400) {
@ -207,8 +233,8 @@ function EmailSection() {
> >
<Trans>Add Email</Trans> <Trans>Add Email</Trans>
</Button> </Button>
</Grid.Col> </Stack>
</Grid> </SimpleGrid>
); );
} }
@ -263,7 +289,7 @@ function ProviderSection({
{data.length == 0 ? ( {data.length == 0 ? (
<Alert <Alert
icon={<IconAlertCircle size='1rem' />} icon={<IconAlertCircle size='1rem' />}
title={t`Not configured`} title={t`Not Configured`}
color='yellow' color='yellow'
> >
<Trans>There are no providers connected to this account.</Trans> <Trans>There are no providers connected to this account.</Trans>
@ -395,11 +421,14 @@ function MfaSection() {
return ( return (
<> <>
<ReauthModal /> <ReauthModal />
<Grid> <SimpleGrid cols={{ xs: 1, md: 2 }} spacing='sm'>
<Grid.Col span={6}>
{data.length == 0 ? ( {data.length == 0 ? (
<Alert icon={<IconAlertCircle size='1rem' />} color='yellow'> <Alert
<Trans>No factors configured</Trans> title={t`Not Configured`}
icon={<IconAlertCircle size='1rem' />}
color='yellow'
>
<Trans>No multi-factor tokens configured for this account</Trans>
</Alert> </Alert>
) : ( ) : (
<Table stickyHeader striped highlightOnHover withTableBorder> <Table stickyHeader striped highlightOnHover withTableBorder>
@ -422,8 +451,6 @@ function MfaSection() {
<Table.Tbody>{rows}</Table.Tbody> <Table.Tbody>{rows}</Table.Tbody>
</Table> </Table>
)} )}
</Grid.Col>
<Grid.Col span={6}>
<MfaAddSection <MfaAddSection
usedFactors={usedFactors} usedFactors={usedFactors}
refetch={refetch} refetch={refetch}
@ -448,8 +475,7 @@ function MfaSection() {
</Title> </Title>
<Code>{recoveryCodes?.used_codes?.join('\n')}</Code> <Code>{recoveryCodes?.used_codes?.join('\n')}</Code>
</Modal> </Modal>
</Grid.Col> </SimpleGrid>
</Grid>
</> </>
); );
} }
@ -554,16 +580,17 @@ function MfaAddSection({
return ( return (
<Stack> <Stack>
<ReauthModal /> <ReauthModal />
<Text>Add Factor</Text> <StylishText size='md'>{t`Add Token`}</StylishText>
{possibleFactors.map((factor) => ( {possibleFactors.map((factor) => (
<Tooltip label={factor.description} key={factor.type}>
<Button <Button
key={factor.type}
onClick={factor.function} onClick={factor.function}
disabled={factor.used} disabled={factor.used}
variant='outline' variant='outline'
> >
{factor.name} {factor.name}
</Button> </Button>
</Tooltip>
))} ))}
<Modal <Modal
opened={totpQrOpen} opened={totpQrOpen}

View File

@ -89,6 +89,17 @@ export const navigate = async (page, url: string) => {
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
}; };
/**
* CLick on the 'tab' element with the provided name
*/
export const loadTab = async (page, tabName) => {
await page
.getByLabel(/panel-tabs-/)
.getByRole('tab', { name: tabName })
.click();
await page.waitForLoadState('networkidle');
};
/** /**
* Perform a 'global search' on the provided page, for the provided query text * Perform a 'global search' on the provided page, for the provided query text
*/ */

View File

@ -2,6 +2,7 @@ import { test } from '../baseFixtures.ts';
import { import {
clearTableFilters, clearTableFilters,
getRowFromCell, getRowFromCell,
loadTab,
navigate, navigate,
setTableChoiceFilter setTableChoiceFilter
} from '../helpers.ts'; } from '../helpers.ts';
@ -12,7 +13,7 @@ test('Build Order - Basic Tests', async ({ page }) => {
// Navigate to the correct build order // Navigate to the correct build order
await page.getByRole('tab', { name: 'Manufacturing', exact: true }).click(); await page.getByRole('tab', { name: 'Manufacturing', exact: true }).click();
await page.getByRole('tab', { name: 'Build Orders', exact: true }).click(); await loadTab(page, 'Build Orders');
await clearTableFilters(page); await clearTableFilters(page);
@ -57,11 +58,11 @@ test('Build Order - Basic Tests', async ({ page }) => {
await page.getByRole('button', { name: 'Cancel' }).click(); await page.getByRole('button', { name: 'Cancel' }).click();
// Click on some tabs // Click on some tabs
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByRole('tab', { name: 'Notes' }).click(); await loadTab(page, 'Notes');
await page.getByRole('tab', { name: 'Incomplete Outputs' }).click(); await loadTab(page, 'Incomplete Outputs');
await page.getByRole('tab', { name: 'Line Items' }).click(); await loadTab(page, 'Line Items');
await page.getByRole('tab', { name: 'Allocated Stock' }).click(); await loadTab(page, 'Allocated Stock');
// Check for expected text in the table // Check for expected text in the table
await page.getByText('R_10R_0402_1%').waitFor(); await page.getByText('R_10R_0402_1%').waitFor();
@ -70,7 +71,7 @@ test('Build Order - Basic Tests', async ({ page }) => {
.waitFor(); .waitFor();
// Check "test results" // Check "test results"
await page.getByRole('tab', { name: 'Test Results' }).click(); await loadTab(page, 'Test Results');
await page.getByText('Quantity: 25').waitFor(); await page.getByText('Quantity: 25').waitFor();
await page.getByText('Continuity Checks').waitFor(); await page.getByText('Continuity Checks').waitFor();
await page await page
@ -80,7 +81,7 @@ test('Build Order - Basic Tests', async ({ page }) => {
await page.getByText('Add Test Result').waitFor(); await page.getByText('Add Test Result').waitFor();
// Click through to the "parent" build // Click through to the "parent" build
await page.getByRole('tab', { name: 'Build Details' }).click(); await loadTab(page, 'Build Details');
await page.getByRole('link', { name: 'BO0010' }).click(); await page.getByRole('link', { name: 'BO0010' }).click();
await page await page
.getByLabel('Build Details') .getByLabel('Build Details')
@ -119,7 +120,7 @@ test('Build Order - Build Outputs', async ({ page }) => {
await doQuickLogin(page); await doQuickLogin(page);
await navigate(page, 'manufacturing/index/'); await navigate(page, 'manufacturing/index/');
await page.getByRole('tab', { name: 'Build Orders', exact: true }).click(); await loadTab(page, 'Build Orders');
await clearTableFilters(page); await clearTableFilters(page);
@ -128,7 +129,7 @@ test('Build Order - Build Outputs', async ({ page }) => {
await page.getByText('Pending').first().waitFor(); await page.getByText('Pending').first().waitFor();
await page.getByRole('cell', { name: 'BO0011' }).click(); await page.getByRole('cell', { name: 'BO0011' }).click();
await page.getByRole('tab', { name: 'Incomplete Outputs' }).click(); await loadTab(page, 'Incomplete Outputs');
// Create a new build output // Create a new build output
await page.getByLabel('action-button-add-build-output').click(); await page.getByLabel('action-button-add-build-output').click();
@ -213,7 +214,7 @@ test('Build Order - Allocation', async ({ page }) => {
await page.getByRole('cell', { name: 'Reel Storage', exact: true }).waitFor(); await page.getByRole('cell', { name: 'Reel Storage', exact: true }).waitFor();
// Navigate to the "Incomplete Outputs" tab // Navigate to the "Incomplete Outputs" tab
await page.getByRole('tab', { name: 'Incomplete Outputs' }).click(); await loadTab(page, 'Incomplete Outputs');
// Find output #7 // Find output #7
const output7 = await page const output7 = await page

View File

@ -1,5 +1,5 @@
import { test } from '../baseFixtures.js'; import { test } from '../baseFixtures.js';
import { navigate } from '../helpers.js'; import { loadTab, navigate } from '../helpers.js';
import { doQuickLogin } from '../login.js'; import { doQuickLogin } from '../login.js';
test('Company', async ({ page }) => { test('Company', async ({ page }) => {
@ -8,23 +8,23 @@ test('Company', async ({ page }) => {
await navigate(page, 'company/1/details'); await navigate(page, 'company/1/details');
await page.getByLabel('Details').getByText('DigiKey Electronics').waitFor(); await page.getByLabel('Details').getByText('DigiKey Electronics').waitFor();
await page.getByRole('cell', { name: 'https://www.digikey.com/' }).waitFor(); await page.getByRole('cell', { name: 'https://www.digikey.com/' }).waitFor();
await page.getByRole('tab', { name: 'Supplied Parts' }).click(); await loadTab(page, 'Supplied Parts');
await page await page
.getByRole('cell', { name: 'RR05P100KDTR-ND', exact: true }) .getByRole('cell', { name: 'RR05P100KDTR-ND', exact: true })
.waitFor(); .waitFor();
await page.getByRole('tab', { name: 'Purchase Orders' }).click(); await loadTab(page, 'Purchase Orders');
await page.getByRole('cell', { name: 'Molex connectors' }).first().waitFor(); await page.getByRole('cell', { name: 'Molex connectors' }).first().waitFor();
await page.getByRole('tab', { name: 'Stock Items' }).click(); await loadTab(page, 'Stock Items');
await page await page
.getByRole('cell', { name: 'Blue plastic enclosure' }) .getByRole('cell', { name: 'Blue plastic enclosure' })
.first() .first()
.waitFor(); .waitFor();
await page.getByRole('tab', { name: 'Contacts' }).click(); await loadTab(page, 'Contacts');
await page.getByRole('cell', { name: 'jimmy.mcleod@digikey.com' }).waitFor(); await page.getByRole('cell', { name: 'jimmy.mcleod@digikey.com' }).waitFor();
await page.getByRole('tab', { name: 'Addresses' }).click(); await loadTab(page, 'Addresses');
await page.getByRole('cell', { name: 'Carla Tunnel' }).waitFor(); await page.getByRole('cell', { name: 'Carla Tunnel' }).waitFor();
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByRole('tab', { name: 'Notes' }).click(); await loadTab(page, 'Notes');
// Let's edit the company details // Let's edit the company details
await page.getByLabel('action-menu-company-actions').click(); await page.getByLabel('action-menu-company-actions').click();

View File

@ -1,5 +1,10 @@
import { test } from '../baseFixtures'; import { test } from '../baseFixtures';
import { clearTableFilters, getRowFromCell, navigate } from '../helpers'; import {
clearTableFilters,
getRowFromCell,
loadTab,
navigate
} from '../helpers';
import { doQuickLogin } from '../login'; import { doQuickLogin } from '../login';
/** /**
@ -19,35 +24,35 @@ test('Parts - Tabs', async ({ page }) => {
await page.getByPlaceholder('Search').fill('1551'); await page.getByPlaceholder('Search').fill('1551');
await page.getByText('1551ABK').click(); await page.getByText('1551ABK').click();
await page.getByRole('tab', { name: 'Allocations' }).click(); await loadTab(page, 'Allocations');
await page.getByRole('tab', { name: 'Used In' }).click(); await loadTab(page, 'Used In');
await page.getByRole('tab', { name: 'Pricing' }).click(); await loadTab(page, 'Pricing');
await page.getByRole('tab', { name: 'Suppliers' }).click(); await loadTab(page, 'Suppliers');
await page.getByRole('tab', { name: 'Purchase Orders' }).click(); await loadTab(page, 'Purchase Orders');
await page.getByRole('tab', { name: 'Scheduling' }).click(); await loadTab(page, 'Scheduling');
await page.getByRole('tab', { name: 'Stock History' }).click(); await loadTab(page, 'Stock History');
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByRole('tab', { name: 'Notes' }).click(); await loadTab(page, 'Notes');
await page.getByRole('tab', { name: 'Related Parts' }).click(); await loadTab(page, 'Related Parts');
// Related Parts // Related Parts
await page.getByText('1551ACLR').click(); await page.getByText('1551ACLR').click();
await page.getByRole('tab', { name: 'Part Details' }).click(); await loadTab(page, 'Part Details');
await page.getByRole('tab', { name: 'Parameters' }).click(); await loadTab(page, 'Parameters');
await page await page
.getByLabel('panel-tabs-part') .getByLabel('panel-tabs-part')
.getByRole('tab', { name: 'Stock', exact: true }) .getByRole('tab', { name: 'Stock', exact: true })
.click(); .click();
await page.getByRole('tab', { name: 'Allocations' }).click(); await loadTab(page, 'Allocations');
await page.getByRole('tab', { name: 'Used In' }).click(); await loadTab(page, 'Used In');
await page.getByRole('tab', { name: 'Pricing' }).click(); await loadTab(page, 'Pricing');
await navigate(page, 'part/category/index/parts'); await navigate(page, 'part/category/index/parts');
await page.getByText('Blue Chair').click(); await page.getByText('Blue Chair').click();
await page.getByRole('tab', { name: 'Bill of Materials' }).click(); await loadTab(page, 'Bill of Materials');
await page.getByRole('tab', { name: 'Build Orders' }).click(); await loadTab(page, 'Build Orders');
}); });
test('Parts - Manufacturer Parts', async ({ page }) => { test('Parts - Manufacturer Parts', async ({ page }) => {
@ -55,11 +60,11 @@ test('Parts - Manufacturer Parts', async ({ page }) => {
await navigate(page, 'part/84/suppliers'); await navigate(page, 'part/84/suppliers');
await page.getByRole('tab', { name: 'Suppliers' }).click(); await loadTab(page, 'Suppliers');
await page.getByText('Hammond Manufacturing').click(); await page.getByText('Hammond Manufacturing').click();
await page.getByRole('tab', { name: 'Parameters' }).click(); await loadTab(page, 'Parameters');
await page.getByRole('tab', { name: 'Suppliers' }).click(); await loadTab(page, 'Suppliers');
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByText('1551ACLR - 1551ACLR').waitFor(); await page.getByText('1551ACLR - 1551ACLR').waitFor();
}); });
@ -68,11 +73,11 @@ test('Parts - Supplier Parts', async ({ page }) => {
await navigate(page, 'part/15/suppliers'); await navigate(page, 'part/15/suppliers');
await page.getByRole('tab', { name: 'Suppliers' }).click(); await loadTab(page, 'Suppliers');
await page.getByRole('cell', { name: 'DIG-84670-SJI' }).click(); await page.getByRole('cell', { name: 'DIG-84670-SJI' }).click();
await page.getByRole('tab', { name: 'Received Stock' }).click(); // await loadTab(page, 'Received Stock'); //
await page.getByRole('tab', { name: 'Purchase Orders' }).click(); await loadTab(page, 'Purchase Orders');
await page.getByRole('tab', { name: 'Pricing' }).click(); await loadTab(page, 'Pricing');
await page.getByText('DIG-84670-SJI - R_550R_0805_1%').waitFor(); await page.getByText('DIG-84670-SJI - R_550R_0805_1%').waitFor();
}); });
@ -81,14 +86,14 @@ test('Parts - Locking', async ({ page }) => {
// Navigate to a known assembly which is *not* locked // Navigate to a known assembly which is *not* locked
await navigate(page, 'part/104/bom'); await navigate(page, 'part/104/bom');
await page.getByRole('tab', { name: 'Bill of Materials' }).click(); await loadTab(page, 'Bill of Materials');
await page.getByLabel('action-button-add-bom-item').waitFor(); await page.getByLabel('action-button-add-bom-item').waitFor();
await page.getByRole('tab', { name: 'Parameters' }).click(); await loadTab(page, 'Parameters');
await page.getByLabel('action-button-add-parameter').waitFor(); await page.getByLabel('action-button-add-parameter').waitFor();
// Navigate to a known assembly which *is* locked // Navigate to a known assembly which *is* locked
await navigate(page, 'part/100/bom'); await navigate(page, 'part/100/bom');
await page.getByRole('tab', { name: 'Bill of Materials' }).click(); await loadTab(page, 'Bill of Materials');
await page.getByLabel('part-lock-icon').waitFor(); await page.getByLabel('part-lock-icon').waitFor();
await page.getByText('Part is Locked', { exact: true }).waitFor(); await page.getByText('Part is Locked', { exact: true }).waitFor();
@ -98,7 +103,7 @@ test('Parts - Locking', async ({ page }) => {
await page.getByText('In Production: 50').waitFor(); await page.getByText('In Production: 50').waitFor();
// Check the "parameters" tab also // Check the "parameters" tab also
await page.getByRole('tab', { name: 'Parameters' }).click(); await loadTab(page, 'Parameters');
await page.getByText('Part parameters cannot be').waitFor(); await page.getByText('Part parameters cannot be').waitFor();
}); });
@ -107,7 +112,7 @@ test('Parts - Allocations', async ({ page }) => {
// Let's look at the allocations for a single stock item // Let's look at the allocations for a single stock item
await navigate(page, 'stock/item/324/'); await navigate(page, 'stock/item/324/');
await page.getByRole('tab', { name: 'Allocations' }).click(); await loadTab(page, 'Allocations');
await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor(); await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor();
await page.getByRole('cell', { name: 'Making some blue chairs' }).waitFor(); await page.getByRole('cell', { name: 'Making some blue chairs' }).waitFor();
@ -124,7 +129,7 @@ test('Parts - Allocations', async ({ page }) => {
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
await page.getByRole('tab', { name: 'Allocations' }).click(); await loadTab(page, 'Allocations');
await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor(); await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor();
await page.getByRole('button', { name: 'Sales Order Allocations' }).waitFor(); await page.getByRole('button', { name: 'Sales Order Allocations' }).waitFor();
@ -177,7 +182,7 @@ test('Parts - Pricing (Nothing, BOM)', async ({ page }) => {
await navigate(page, 'part/82/pricing'); await navigate(page, 'part/82/pricing');
await page.getByText('Small plastic enclosure, black').waitFor(); await page.getByText('Small plastic enclosure, black').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click(); await loadTab(page, 'Part Pricing');
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor(); await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor(); await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
await page.getByText('Last Updated').waitFor(); await page.getByText('Last Updated').waitFor();
@ -188,7 +193,7 @@ test('Parts - Pricing (Nothing, BOM)', async ({ page }) => {
// Part with history // Part with history
await navigate(page, 'part/108/pricing'); await navigate(page, 'part/108/pricing');
await page.getByText('A chair - with blue paint').waitFor(); await page.getByText('A chair - with blue paint').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click(); await loadTab(page, 'Part Pricing');
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor(); await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor(); await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
await page.getByText('Last Updated').waitFor(); await page.getByText('Last Updated').waitFor();
@ -226,7 +231,7 @@ test('Parts - Pricing (Supplier)', async ({ page }) => {
// Part // Part
await navigate(page, 'part/55/pricing'); await navigate(page, 'part/55/pricing');
await page.getByText('Ceramic capacitor, 100nF in').waitFor(); await page.getByText('Ceramic capacitor, 100nF in').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click(); await loadTab(page, 'Part Pricing');
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor(); await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor(); await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
await page.getByText('Last Updated').waitFor(); await page.getByText('Last Updated').waitFor();
@ -252,7 +257,7 @@ test('Parts - Pricing (Variant)', async ({ page }) => {
// Part // Part
await navigate(page, 'part/106/pricing'); await navigate(page, 'part/106/pricing');
await page.getByText('A chair - available in multiple colors').waitFor(); await page.getByText('A chair - available in multiple colors').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click(); await loadTab(page, 'Part Pricing');
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor(); await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor(); await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
await page.getByText('Last Updated').waitFor(); await page.getByText('Last Updated').waitFor();
@ -278,7 +283,7 @@ test('Parts - Pricing (Internal)', async ({ page }) => {
// Part // Part
await navigate(page, 'part/65/pricing'); await navigate(page, 'part/65/pricing');
await page.getByText('Socket head cap screw, M2').waitFor(); await page.getByText('Socket head cap screw, M2').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click(); await loadTab(page, 'Part Pricing');
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor(); await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor(); await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
await page.getByText('Last Updated').waitFor(); await page.getByText('Last Updated').waitFor();
@ -303,7 +308,7 @@ test('Parts - Pricing (Purchase)', async ({ page }) => {
// Part // Part
await navigate(page, 'part/69/pricing'); await navigate(page, 'part/69/pricing');
await page.getByText('1.25mm Pitch, PicoBlade PCB').waitFor(); await page.getByText('1.25mm Pitch, PicoBlade PCB').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click(); await loadTab(page, 'Part Pricing');
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor(); await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor(); await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
await page.getByText('Last Updated').waitFor(); await page.getByText('Last Updated').waitFor();

View File

@ -3,6 +3,7 @@ import {
clearTableFilters, clearTableFilters,
clickButtonIfVisible, clickButtonIfVisible,
clickOnRowMenu, clickOnRowMenu,
loadTab,
navigate, navigate,
openFilterDrawer, openFilterDrawer,
setTableChoiceFilter setTableChoiceFilter
@ -13,7 +14,7 @@ test('Purchase Orders - List', async ({ page }) => {
await doQuickLogin(page); await doQuickLogin(page);
await page.getByRole('tab', { name: 'Purchasing' }).click(); await page.getByRole('tab', { name: 'Purchasing' }).click();
await page.getByRole('tab', { name: 'Purchase Orders' }).click(); await loadTab(page, 'Purchase Orders');
await clearTableFilters(page); await clearTableFilters(page);
@ -101,29 +102,32 @@ test('Purchase Orders - General', async ({ page }) => {
await doQuickLogin(page); await doQuickLogin(page);
await page.getByRole('tab', { name: 'Purchasing' }).click(); await page.getByRole('tab', { name: 'Purchasing' }).click();
await page.getByRole('cell', { name: 'PO0012' }).click(); await page.getByRole('cell', { name: 'PO0012' }).click();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await page.getByRole('tab', { name: 'Line Items' }).click(); await loadTab(page, 'Line Items');
await page.getByRole('tab', { name: 'Received Stock' }).click(); await loadTab(page, 'Received Stock');
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByRole('tab', { name: 'Purchasing' }).click(); await page.getByRole('tab', { name: 'Purchasing' }).click();
await page.getByRole('tab', { name: 'Suppliers' }).click(); await loadTab(page, 'Suppliers');
await page.getByText('Arrow', { exact: true }).click(); await page.getByText('Arrow', { exact: true }).click();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await page.getByRole('tab', { name: 'Supplied Parts' }).click(); await loadTab(page, 'Supplied Parts');
await page.getByRole('tab', { name: 'Purchase Orders' }).click(); await loadTab(page, 'Purchase Orders');
await page.getByRole('tab', { name: 'Stock Items' }).click(); await loadTab(page, 'Stock Items');
await page.getByRole('tab', { name: 'Contacts' }).click(); await loadTab(page, 'Contacts');
await page.getByRole('tab', { name: 'Addresses' }).click(); await loadTab(page, 'Addresses');
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByRole('tab', { name: 'Purchasing' }).click(); await page.getByRole('tab', { name: 'Purchasing' }).click();
await page.getByRole('tab', { name: 'Manufacturers' }).click(); await loadTab(page, 'Manufacturers');
await page.getByText('AVX Corporation').click(); await page.getByText('AVX Corporation').click();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await page.getByRole('tab', { name: 'Addresses' }).click(); await loadTab(page, 'Addresses');
await page.getByRole('cell', { name: 'West Branch' }).click(); await page.getByRole('cell', { name: 'West Branch' }).click();
await page.locator('.mantine-ScrollArea-root').click(); await page.locator('.mantine-ScrollArea-root').click();
await page await page
@ -151,7 +155,7 @@ test('Purchase Orders - Filters', async ({ page }) => {
await doQuickLogin(page, 'reader', 'readonly'); await doQuickLogin(page, 'reader', 'readonly');
await page.getByRole('tab', { name: 'Purchasing' }).click(); await page.getByRole('tab', { name: 'Purchasing' }).click();
await page.getByRole('tab', { name: 'Purchase Orders' }).click(); await loadTab(page, 'Purchase Orders');
// Open filters drawer // Open filters drawer
await openFilterDrawer(page); await openFilterDrawer(page);
@ -197,7 +201,7 @@ test('Purchase Orders - Order Parts', async ({ page }) => {
// Open "Order Parts" wizard from the "Stock Items" table // Open "Order Parts" wizard from the "Stock Items" table
await page.getByRole('tab', { name: 'Stock' }).click(); await page.getByRole('tab', { name: 'Stock' }).click();
await page.getByRole('tab', { name: 'Stock Items' }).click(); await loadTab(page, 'Stock Items');
// Select multiple stock items // Select multiple stock items
for (let ii = 2; ii < 7; ii += 2) { for (let ii = 2; ii < 7; ii += 2) {
@ -257,10 +261,10 @@ test('Purchase Orders - Receive Items', async ({ page }) => {
await page.getByRole('tab', { name: 'Purchasing' }).click(); await page.getByRole('tab', { name: 'Purchasing' }).click();
await page.getByRole('cell', { name: 'PO0014' }).click(); await page.getByRole('cell', { name: 'PO0014' }).click();
await page.getByRole('tab', { name: 'Order Details' }).click(); await loadTab(page, 'Order Details');
// Select all line items to receive // Select all line items to receive
await page.getByRole('tab', { name: 'Line Items' }).click(); await loadTab(page, 'Line Items');
await page.getByLabel('Select all records').click(); await page.getByLabel('Select all records').click();
await page.waitForTimeout(200); await page.waitForTimeout(200);
@ -311,7 +315,7 @@ test('Purchase Orders - Receive Items', async ({ page }) => {
await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('button', { name: 'Submit' }).click();
await page.getByText('Items received').waitFor(); await page.getByText('Items received').waitFor();
await page.getByRole('tab', { name: 'Received Stock' }).click(); await loadTab(page, 'Received Stock');
await clearTableFilters(page); await clearTableFilters(page);
await page.getByRole('cell', { name: 'my-batch-code' }).first().waitFor(); await page.getByRole('cell', { name: 'my-batch-code' }).first().waitFor();

View File

@ -2,6 +2,7 @@ import { test } from '../baseFixtures.ts';
import { import {
clearTableFilters, clearTableFilters,
globalSearch, globalSearch,
loadTab,
navigate, navigate,
setTableChoiceFilter setTableChoiceFilter
} from '../helpers.ts'; } from '../helpers.ts';
@ -13,27 +14,27 @@ test('Sales Orders - Tabs', async ({ page }) => {
await navigate(page, 'sales/index/'); await navigate(page, 'sales/index/');
await page.waitForURL('**/platform/sales/**'); await page.waitForURL('**/platform/sales/**');
await page.getByRole('tab', { name: 'Sales Orders' }).click(); await loadTab(page, 'Sales Orders');
await page.waitForURL('**/platform/sales/index/salesorders'); await page.waitForURL('**/platform/sales/index/salesorders');
await page.getByRole('tab', { name: 'Return Orders' }).click(); await loadTab(page, 'Return Orders');
// Customers // Customers
await page.getByRole('tab', { name: 'Customers' }).click(); await loadTab(page, 'Customers');
await page.getByText('Customer A').click(); await page.getByText('Customer A').click();
await page.getByRole('tab', { name: 'Notes' }).click(); await loadTab(page, 'Notes');
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByRole('tab', { name: 'Contacts' }).click(); await loadTab(page, 'Contacts');
await page.getByRole('tab', { name: 'Assigned Stock' }).click(); await loadTab(page, 'Assigned Stock');
await page.getByRole('tab', { name: 'Return Orders' }).click(); await loadTab(page, 'Return Orders');
await page.getByRole('tab', { name: 'Sales Orders' }).click(); await loadTab(page, 'Sales Orders');
await page.getByRole('tab', { name: 'Contacts' }).click(); await loadTab(page, 'Contacts');
await page.getByRole('cell', { name: 'Dorathy Gross' }).waitFor(); await page.getByRole('cell', { name: 'Dorathy Gross' }).waitFor();
await page await page
.getByRole('row', { name: 'Dorathy Gross dorathy.gross@customer.com' }) .getByRole('row', { name: 'Dorathy Gross dorathy.gross@customer.com' })
.waitFor(); .waitFor();
// Sales Order Details // Sales Order Details
await page.getByRole('tab', { name: 'Sales Orders' }).click(); await loadTab(page, 'Sales Orders');
await clearTableFilters(page); await clearTableFilters(page);
@ -42,30 +43,30 @@ test('Sales Orders - Tabs', async ({ page }) => {
.getByLabel('Order Details') .getByLabel('Order Details')
.getByText('Selling some stuff') .getByText('Selling some stuff')
.waitFor(); .waitFor();
await page.getByRole('tab', { name: 'Line Items' }).click(); await loadTab(page, 'Line Items');
await page.getByRole('tab', { name: 'Shipments' }).click(); await loadTab(page, 'Shipments');
await page.getByRole('tab', { name: 'Build Orders' }).click(); await loadTab(page, 'Build Orders');
await page.getByText('No records found').first().waitFor(); await page.getByText('No records found').first().waitFor();
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByText('No attachments found').first().waitFor(); await page.getByText('No attachments found').first().waitFor();
await page.getByRole('tab', { name: 'Notes' }).click(); await loadTab(page, 'Notes');
await page.getByRole('tab', { name: 'Order Details' }).click(); await loadTab(page, 'Order Details');
// Return Order Details // Return Order Details
await page.getByRole('link', { name: 'Customer A' }).click(); await page.getByRole('link', { name: 'Customer A' }).click();
await page.getByRole('tab', { name: 'Return Orders' }).click(); await loadTab(page, 'Return Orders');
await page.getByRole('cell', { name: 'RMA-' }).click(); await page.getByRole('cell', { name: 'RMA-' }).click();
await page.getByText('RMA-0001', { exact: true }).waitFor(); await page.getByText('RMA-0001', { exact: true }).waitFor();
await page.getByRole('tab', { name: 'Line Items' }).click(); await loadTab(page, 'Line Items');
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByRole('tab', { name: 'Notes' }).click(); await loadTab(page, 'Notes');
}); });
test('Sales Orders - Basic Tests', async ({ page }) => { test('Sales Orders - Basic Tests', async ({ page }) => {
await doQuickLogin(page); await doQuickLogin(page);
await page.getByRole('tab', { name: 'Sales' }).click(); await page.getByRole('tab', { name: 'Sales' }).click();
await page.getByRole('tab', { name: 'Sales Orders' }).click(); await loadTab(page, 'Sales Orders');
await clearTableFilters(page); await clearTableFilters(page);
@ -104,12 +105,12 @@ test('Sales Orders - Shipments', async ({ page }) => {
await doQuickLogin(page); await doQuickLogin(page);
await page.getByRole('tab', { name: 'Sales' }).click(); await page.getByRole('tab', { name: 'Sales' }).click();
await page.getByRole('tab', { name: 'Sales Orders' }).click(); await loadTab(page, 'Sales Orders');
await clearTableFilters(page); await clearTableFilters(page);
// Click through to a particular sales order // Click through to a particular sales order
await page.getByRole('cell', { name: 'SO0006' }).first().click(); await page.getByRole('cell', { name: 'SO0006' }).first().click();
await page.getByRole('tab', { name: 'Shipments' }).click(); await loadTab(page, 'Shipments');
// Create a new shipment // Create a new shipment
await page.getByLabel('action-button-add-shipment').click(); await page.getByLabel('action-button-add-shipment').click();
@ -155,14 +156,14 @@ test('Sales Orders - Shipments', async ({ page }) => {
await page.getByRole('menuitem', { name: 'View Shipment' }).click(); await page.getByRole('menuitem', { name: 'View Shipment' }).click();
// Click through the various tabs // Click through the various tabs
await page.getByRole('tab', { name: 'Attachments' }).click(); await loadTab(page, 'Attachments');
await page.getByRole('tab', { name: 'Notes' }).click(); await loadTab(page, 'Notes');
await page.getByRole('tab', { name: 'Allocated Stock' }).click(); await loadTab(page, 'Allocated Stock');
// Ensure assigned items table loads correctly // Ensure assigned items table loads correctly
await page.getByRole('cell', { name: 'BATCH-001' }).first().waitFor(); await page.getByRole('cell', { name: 'BATCH-001' }).first().waitFor();
await page.getByRole('tab', { name: 'Shipment Details' }).click(); await loadTab(page, 'Shipment Details');
// The "new" tracking number should be visible // The "new" tracking number should be visible
await page.getByText(tracking_number).waitFor(); await page.getByText(tracking_number).waitFor();
@ -171,7 +172,7 @@ test('Sales Orders - Shipments', async ({ page }) => {
await page.getByRole('link', { name: 'SO0006' }).click(); await page.getByRole('link', { name: 'SO0006' }).click();
// Let's try to allocate some stock // Let's try to allocate some stock
await page.getByRole('tab', { name: 'Line Items' }).click(); await loadTab(page, 'Line Items');
await page.getByLabel('row-action-menu-1').click(); await page.getByLabel('row-action-menu-1').click();
await page.getByRole('menuitem', { name: 'Allocate stock' }).click(); await page.getByRole('menuitem', { name: 'Allocate stock' }).click();
await page await page

View File

@ -2,6 +2,7 @@ import { test } from '../baseFixtures.js';
import { import {
clearTableFilters, clearTableFilters,
clickButtonIfVisible, clickButtonIfVisible,
loadTab,
navigate, navigate,
openFilterDrawer, openFilterDrawer,
setTableChoiceFilter setTableChoiceFilter
@ -14,28 +15,28 @@ test('Stock - Basic Tests', async ({ page }) => {
await navigate(page, 'stock/location/index/'); await navigate(page, 'stock/location/index/');
await page.waitForURL('**/platform/stock/location/**'); await page.waitForURL('**/platform/stock/location/**');
await page.getByRole('tab', { name: 'Location Details' }).click(); await loadTab(page, 'Location Details');
await page.waitForURL('**/platform/stock/location/index/details'); await page.waitForURL('**/platform/stock/location/index/details');
await page.getByRole('tab', { name: 'Stock Items' }).click(); await loadTab(page, 'Stock Items');
await page.getByText('1551ABK').first().click(); await page.getByText('1551ABK').first().click();
await page.getByRole('tab', { name: 'Stock', exact: true }).click(); await page.getByRole('tab', { name: 'Stock', exact: true }).click();
await page.waitForURL('**/platform/stock/**'); await page.waitForURL('**/platform/stock/**');
await page.getByRole('tab', { name: 'Stock Locations' }).click(); await loadTab(page, 'Stock Locations');
await page.getByRole('cell', { name: 'Electronics Lab' }).first().click(); await page.getByRole('cell', { name: 'Electronics Lab' }).first().click();
await page.getByRole('tab', { name: 'Default Parts' }).click(); await loadTab(page, 'Default Parts');
await page.getByRole('tab', { name: 'Stock Locations' }).click(); await loadTab(page, 'Stock Locations');
await page.getByRole('tab', { name: 'Stock Items' }).click(); await loadTab(page, 'Stock Items');
await page.getByRole('tab', { name: 'Location Details' }).click(); await loadTab(page, 'Location Details');
await navigate(page, 'stock/item/1194/details'); await navigate(page, 'stock/item/1194/details');
await page.getByText('D.123 | Doohickey').waitFor(); await page.getByText('D.123 | Doohickey').waitFor();
await page.getByText('Batch Code: BX-123-2024-2-7').waitFor(); await page.getByText('Batch Code: BX-123-2024-2-7').waitFor();
await page.getByRole('tab', { name: 'Stock Tracking' }).click(); await loadTab(page, 'Stock Tracking');
await page.getByRole('tab', { name: 'Test Data' }).click(); await loadTab(page, 'Test Data');
await page.getByText('395c6d5586e5fb656901d047be27e1f7').waitFor(); await page.getByText('395c6d5586e5fb656901d047be27e1f7').waitFor();
await page.getByRole('tab', { name: 'Installed Items' }).click(); await loadTab(page, 'Installed Items');
}); });
test('Stock - Location Tree', async ({ page }) => { test('Stock - Location Tree', async ({ page }) => {
@ -43,7 +44,7 @@ test('Stock - Location Tree', async ({ page }) => {
await navigate(page, 'stock/location/index/'); await navigate(page, 'stock/location/index/');
await page.waitForURL('**/platform/stock/location/**'); await page.waitForURL('**/platform/stock/location/**');
await page.getByRole('tab', { name: 'Location Details' }).click(); await loadTab(page, 'Location Details');
await page.getByLabel('nav-breadcrumb-action').click(); await page.getByLabel('nav-breadcrumb-action').click();
await page.getByLabel('nav-tree-toggle-1}').click(); await page.getByLabel('nav-tree-toggle-1}').click();
@ -59,7 +60,7 @@ test('Stock - Filters', async ({ page }) => {
await doQuickLogin(page, 'steven', 'wizardstaff'); await doQuickLogin(page, 'steven', 'wizardstaff');
await navigate(page, 'stock/location/index/'); await navigate(page, 'stock/location/index/');
await page.getByRole('tab', { name: 'Stock Items' }).click(); await loadTab(page, 'Stock Items');
await openFilterDrawer(page); await openFilterDrawer(page);
await clickButtonIfVisible(page, 'Clear Filters'); await clickButtonIfVisible(page, 'Clear Filters');
@ -238,7 +239,7 @@ test('Stock - Tracking', async ({ page }) => {
await page.getByRole('link', { name: 'Widget Assembly # 2' }).waitFor(); await page.getByRole('link', { name: 'Widget Assembly # 2' }).waitFor();
// Navigate to the "stock tracking" tab // Navigate to the "stock tracking" tab
await page.getByRole('tab', { name: 'Stock Tracking' }).click(); await loadTab(page, 'Stock Tracking');
await page.getByText('- - Factory/Office Block/Room').first().waitFor(); await page.getByText('- - Factory/Office Block/Room').first().waitFor();
await page.getByRole('link', { name: 'Widget Assembly' }).waitFor(); await page.getByRole('link', { name: 'Widget Assembly' }).waitFor();
await page.getByRole('cell', { name: 'Installed into assembly' }).waitFor(); await page.getByRole('cell', { name: 'Installed into assembly' }).waitFor();

View File

@ -1,6 +1,6 @@
import test from 'playwright/test'; import test from 'playwright/test';
import { navigate } from './helpers.js'; import { loadTab, navigate } from './helpers.js';
import { doQuickLogin } from './login.js'; import { doQuickLogin } from './login.js';
import { setPluginState, setSettingState } from './settings.js'; import { setPluginState, setSettingState } from './settings.js';
@ -29,19 +29,18 @@ test('Plugins - Panels', async ({ page, request }) => {
await navigate(page, 'part/69/'); await navigate(page, 'part/69/');
// Ensure basic part tab is available // Ensure basic part tab is available
await page.getByRole('tab', { name: 'Part Details' }).waitFor(); await loadTab(page, 'Part Details');
// Allow time for the plugin panels to load (they are loaded asynchronously) // Allow time for the plugin panels to load (they are loaded asynchronously)
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
// Check out each of the plugin panels // Check out each of the plugin panels
await loadTab(page, 'Broken Panel');
await page.getByRole('tab', { name: 'Broken Panel' }).click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByText('Error occurred while loading plugin content').waitFor(); await page.getByText('Error occurred while loading plugin content').waitFor();
await page.getByRole('tab', { name: 'Dynamic Panel' }).click(); await loadTab(page, 'Dynamic Panel');
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByText('Instance ID: 69'); await page.getByText('Instance ID: 69');
@ -49,7 +48,7 @@ test('Plugins - Panels', async ({ page, request }) => {
.getByText('This panel has been dynamically rendered by the plugin system') .getByText('This panel has been dynamically rendered by the plugin system')
.waitFor(); .waitFor();
await page.getByRole('tab', { name: 'Part Panel', exact: true }).click(); await loadTab(page, 'Part Panel');
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByText('This content has been rendered by a custom plugin'); await page.getByText('This content has been rendered by a custom plugin');

View File

@ -1,5 +1,5 @@
import { expect, test } from './baseFixtures.js'; import { expect, test } from './baseFixtures.js';
import { navigate } from './helpers.js'; import { loadTab, navigate } from './helpers.js';
import { doQuickLogin } from './login.js'; import { doQuickLogin } from './login.js';
import { setPluginState } from './settings.js'; import { setPluginState } from './settings.js';
@ -14,7 +14,7 @@ test('Label Printing', async ({ page }) => {
await navigate(page, 'stock/location/index/'); await navigate(page, 'stock/location/index/');
await page.waitForURL('**/platform/stock/location/**'); await page.waitForURL('**/platform/stock/location/**');
await page.getByRole('tab', { name: 'Stock Items' }).click(); await loadTab(page, 'Stock Items');
// Select some labels // Select some labels
await page.getByLabel('Select record 1', { exact: true }).click(); await page.getByLabel('Select record 1', { exact: true }).click();
@ -60,7 +60,7 @@ test('Report Printing', async ({ page }) => {
// Navigate to a specific PurchaseOrder // Navigate to a specific PurchaseOrder
await page.getByRole('tab', { name: 'Purchasing' }).click(); await page.getByRole('tab', { name: 'Purchasing' }).click();
await page.getByRole('tab', { name: 'Purchase Orders' }).click(); await loadTab(page, 'Purchase Orders');
await page.getByRole('cell', { name: 'PO0009' }).click(); await page.getByRole('cell', { name: 'PO0009' }).click();
@ -98,7 +98,7 @@ test('Report Editing', async ({ page, request }) => {
// Navigate to the admin center // Navigate to the admin center
await page.getByRole('button', { name: 'admin' }).click(); await page.getByRole('button', { name: 'admin' }).click();
await page.getByRole('menuitem', { name: 'Admin Center' }).click(); await page.getByRole('menuitem', { name: 'Admin Center' }).click();
await page.getByRole('tab', { name: 'Label Templates' }).click(); await loadTab(page, 'Label Templates');
await page await page
.getByRole('cell', { name: 'InvenTree Stock Item Label (' }) .getByRole('cell', { name: 'InvenTree Stock Item Label (' })
.click(); .click();

View File

@ -1,6 +1,6 @@
import { expect, test } from './baseFixtures.js'; import { expect, test } from './baseFixtures.js';
import { apiUrl } from './defaults.js'; import { apiUrl } from './defaults.js';
import { getRowFromCell, navigate } from './helpers.js'; import { getRowFromCell, loadTab, navigate } from './helpers.js';
import { doQuickLogin } from './login.js'; import { doQuickLogin } from './login.js';
import { setSettingState } from './settings.js'; import { setSettingState } from './settings.js';
@ -49,47 +49,47 @@ test('Settings - Admin', async ({ page }) => {
// User settings // User settings
await page.getByRole('button', { name: 'admin' }).click(); await page.getByRole('button', { name: 'admin' }).click();
await page.getByRole('menuitem', { name: 'Account settings' }).click(); await page.getByRole('menuitem', { name: 'Account settings' }).click();
await page.getByRole('tab', { name: 'Security' }).click(); await loadTab(page, 'Security');
await page.getByRole('tab', { name: 'Display Options' }).click(); await loadTab(page, 'Display Options');
await page.getByText('Date Format').waitFor(); await page.getByText('Date Format').waitFor();
await page.getByRole('tab', { name: 'Search' }).click(); await loadTab(page, 'Search');
await page.getByText('Regex Search').waitFor(); await page.getByText('Regex Search').waitFor();
await page.getByRole('tab', { name: 'Notifications' }).click(); await loadTab(page, 'Notifications');
await page.getByRole('tab', { name: 'Reporting' }).click(); await loadTab(page, 'Reporting');
await page.getByText('Inline report display').waitFor(); await page.getByText('Inline report display').waitFor();
// System Settings // System Settings
await page.locator('label').filter({ hasText: 'System Settings' }).click(); await page.locator('label').filter({ hasText: 'System Settings' }).click();
await page.getByText('Base URL', { exact: true }).waitFor(); await page.getByText('Base URL', { exact: true }).waitFor();
await page.getByRole('tab', { name: 'Login' }).click(); await loadTab(page, 'Login');
await page.getByRole('tab', { name: 'Barcodes' }).click(); await loadTab(page, 'Barcodes');
await page.getByRole('tab', { name: 'Notifications' }).click(); await loadTab(page, 'Notifications');
await page.getByRole('tab', { name: 'Pricing' }).click(); await loadTab(page, 'Pricing');
await page.getByRole('tab', { name: 'Labels' }).click(); await loadTab(page, 'Labels');
await page.getByRole('tab', { name: 'Reporting' }).click(); await loadTab(page, 'Reporting');
await page.getByRole('tab', { name: 'Build Orders' }).click(); await loadTab(page, 'Build Orders');
await page.getByRole('tab', { name: 'Purchase Orders' }).click(); await loadTab(page, 'Purchase Orders');
await page.getByRole('tab', { name: 'Sales Orders' }).click(); await loadTab(page, 'Sales Orders');
await page.getByRole('tab', { name: 'Return Orders' }).click(); await loadTab(page, 'Return Orders');
// Admin Center // Admin Center
await page.getByRole('button', { name: 'admin' }).click(); await page.getByRole('button', { name: 'admin' }).click();
await page.getByRole('menuitem', { name: 'Admin Center' }).click(); await page.getByRole('menuitem', { name: 'Admin Center' }).click();
await page.getByRole('tab', { name: 'Background Tasks' }).click(); await loadTab(page, 'Background Tasks');
await page.getByRole('tab', { name: 'Error Reports' }).click(); await loadTab(page, 'Error Reports');
await page.getByRole('tab', { name: 'Currencies' }).click(); await loadTab(page, 'Currencies');
await page.getByRole('tab', { name: 'Project Codes' }).click(); await loadTab(page, 'Project Codes');
await page.getByRole('tab', { name: 'Custom Units' }).click(); await loadTab(page, 'Custom Units');
await page.getByRole('tab', { name: 'Part Parameters' }).click(); await loadTab(page, 'Part Parameters');
await page.getByRole('tab', { name: 'Category Parameters' }).click(); await loadTab(page, 'Category Parameters');
await page.getByRole('tab', { name: 'Label Templates' }).click(); await loadTab(page, 'Label Templates');
await page.getByRole('tab', { name: 'Report Templates' }).click(); await loadTab(page, 'Report Templates');
await page.getByRole('tab', { name: 'Plugins' }).click(); await loadTab(page, 'Plugins');
// Adjust some "location type" items // Adjust some "location type" items
await page.getByRole('tab', { name: 'Location Types' }).click(); await loadTab(page, 'Location Types');
// Edit first item ('Room') // Edit first item ('Room')
const roomCell = await page.getByRole('cell', { name: 'Room', exact: true }); const roomCell = await page.getByRole('cell', { name: 'Room', exact: true });
@ -166,7 +166,7 @@ test('Settings - Admin - Barcode History', async ({ page, request }) => {
await page.getByRole('button', { name: 'admin' }).click(); await page.getByRole('button', { name: 'admin' }).click();
await page.getByRole('menuitem', { name: 'Admin Center' }).click(); await page.getByRole('menuitem', { name: 'Admin Center' }).click();
await page.getByRole('tab', { name: 'Barcode Scans' }).click(); await loadTab(page, 'Barcode Scans');
await page.waitForTimeout(500); await page.waitForTimeout(500);
@ -193,8 +193,8 @@ test('Settings - Admin - Unauthorized', async ({ page }) => {
await navigate(page, 'settings/user/'); await navigate(page, 'settings/user/');
await page.waitForURL('**/settings/user/**'); await page.waitForURL('**/settings/user/**');
await page.getByRole('tab', { name: 'Display Options' }).click(); await loadTab(page, 'Display Options');
await page.getByRole('tab', { name: 'Account' }).click(); await loadTab(page, 'Account');
// Try to access global settings page // Try to access global settings page
await navigate(page, 'settings/system/'); await navigate(page, 'settings/system/');
@ -205,3 +205,25 @@ test('Settings - Admin - Unauthorized', async ({ page }) => {
.getByRole('button', { name: 'Return to the index page' }) .getByRole('button', { name: 'Return to the index page' })
.waitFor(); .waitFor();
}); });
// Test for user auth configuration
test('Settings - Auth - Email', async ({ page }) => {
await doQuickLogin(page, 'allaccess', 'nolimits');
await navigate(page, 'settings/user/');
await loadTab(page, 'Security');
await page.getByText('Currently no email addresses are registered').waitFor();
await page.getByLabel('email-address-input').fill('test-email@domain.org');
await page.getByLabel('email-address-submit').click();
await page.getByText('Unverified', { exact: true }).waitFor();
await page.getByLabel('test-email@domain.').click();
await page.getByRole('button', { name: 'Make Primary' }).click();
await page.getByText('Primary', { exact: true }).waitFor();
await page.getByRole('button', { name: 'Remove' }).click();
await page.getByText('Currently no email addresses are registered').waitFor();
await page.waitForTimeout(2500);
});