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:
parent
44cca7ddf2
commit
e447e4037b
24
src/frontend/src/components/SplashScreen.tsx
Normal file
24
src/frontend/src/components/SplashScreen.tsx
Normal 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}</>;
|
||||||
|
}
|
||||||
|
}
|
@ -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 || {};
|
||||||
|
@ -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,49 +105,53 @@ export default function Set_Password() {
|
|||||||
return (
|
return (
|
||||||
<LanguageContext>
|
<LanguageContext>
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<Center mih='100vh'>
|
<SplashScreen>
|
||||||
<Container w='md' miw={425}>
|
<Center mih='100vh'>
|
||||||
<Stack>
|
<Container w='md' miw={425}>
|
||||||
<StylishText size='xl'>{t`Reset Password`}</StylishText>
|
<Paper p='xl' withBorder>
|
||||||
<Divider />
|
<Stack>
|
||||||
{user.username() && (
|
<StylishText size='xl'>{t`Reset Password`}</StylishText>
|
||||||
<Paper>
|
<Divider />
|
||||||
<Group>
|
{user.username() && (
|
||||||
<StylishText size='md'>{t`User`}</StylishText>
|
<Paper>
|
||||||
<Text>{user.username()}</Text>
|
<Group>
|
||||||
</Group>
|
<StylishText size='md'>{t`Username`}</StylishText>
|
||||||
</Paper>
|
<Text>{user.username()}</Text>
|
||||||
)}
|
</Group>
|
||||||
<Divider />
|
</Paper>
|
||||||
<Stack gap='xs'>
|
)}
|
||||||
<PasswordInput
|
<Divider />
|
||||||
required
|
<Stack gap='xs'>
|
||||||
aria-label='password'
|
<PasswordInput
|
||||||
label={t`Current Password`}
|
required
|
||||||
description={t`Enter your current password`}
|
aria-label='password'
|
||||||
{...simpleForm.getInputProps('current_password')}
|
label={t`Current Password`}
|
||||||
/>
|
description={t`Enter your current password`}
|
||||||
<PasswordInput
|
{...simpleForm.getInputProps('current_password')}
|
||||||
required
|
/>
|
||||||
aria-label='input-password-1'
|
<PasswordInput
|
||||||
label={t`New Password`}
|
required
|
||||||
description={t`Enter your new password`}
|
aria-label='input-password-1'
|
||||||
{...simpleForm.getInputProps('new_password1')}
|
label={t`New Password`}
|
||||||
/>
|
description={t`Enter your new password`}
|
||||||
<PasswordInput
|
{...simpleForm.getInputProps('new_password1')}
|
||||||
required
|
/>
|
||||||
aria-label='input-password-2'
|
<PasswordInput
|
||||||
label={t`Confirm New Password`}
|
required
|
||||||
description={t`Confirm your new password`}
|
aria-label='input-password-2'
|
||||||
{...simpleForm.getInputProps('new_password2')}
|
label={t`Confirm New Password`}
|
||||||
/>
|
description={t`Confirm your new password`}
|
||||||
</Stack>
|
{...simpleForm.getInputProps('new_password2')}
|
||||||
<Button type='submit' onClick={handleSet}>
|
/>
|
||||||
<Trans>Confirm</Trans>
|
</Stack>
|
||||||
</Button>
|
<Button type='submit' onClick={handleSet}>
|
||||||
</Stack>
|
<Trans>Confirm</Trans>
|
||||||
</Container>
|
</Button>
|
||||||
</Center>
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
</Center>
|
||||||
|
</SplashScreen>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
</LanguageContext>
|
</LanguageContext>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,38 +24,40 @@ export default function MFALogin() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LanguageContext>
|
<LanguageContext>
|
||||||
<Center mih='100vh'>
|
<SplashScreen>
|
||||||
<Container w='md' miw={425}>
|
<Center mih='100vh'>
|
||||||
<Stack>
|
<Container w='md' miw={425}>
|
||||||
<Title>
|
<Paper p='xl' withBorder>
|
||||||
<Trans>MFA Login</Trans>
|
<Stack>
|
||||||
</Title>
|
<StylishText size='xl'>{t`Multi-Factor Login`}</StylishText>
|
||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label={t`TOTP Code`}
|
label={t`TOTP Code`}
|
||||||
name='TOTP'
|
name='TOTP'
|
||||||
description={t`Enter your TOTP or recovery code`}
|
description={t`Enter your TOTP or recovery code`}
|
||||||
{...simpleForm.getInputProps('code')}
|
{...simpleForm.getInputProps('code')}
|
||||||
error={loginError}
|
error={loginError}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleMfaLogin(
|
handleMfaLogin(
|
||||||
navigate,
|
navigate,
|
||||||
location,
|
location,
|
||||||
simpleForm.values,
|
simpleForm.values,
|
||||||
setLoginError
|
setLoginError
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Trans>Log in</Trans>
|
<Trans>Log In</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Paper>
|
||||||
</Center>
|
</Container>
|
||||||
|
</Center>
|
||||||
|
</SplashScreen>
|
||||||
</LanguageContext>
|
</LanguageContext>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,91 +46,116 @@ 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>
|
||||||
<EmailSection />
|
<StylishText size='lg'>{t`Email Addresses`}</StylishText>
|
||||||
<Title order={5}>
|
</Accordion.Control>
|
||||||
<Trans>Single Sign On</Trans>
|
<Accordion.Panel>
|
||||||
</Title>
|
<EmailSection />
|
||||||
{sso_enabled() ? (
|
</Accordion.Panel>
|
||||||
<ProviderSection auth_config={auth_config} />
|
</Accordion.Item>
|
||||||
) : (
|
<Accordion.Item value='sso'>
|
||||||
<Alert
|
<Accordion.Control>
|
||||||
icon={<IconAlertCircle size='1rem' />}
|
<StylishText size='lg'>{t`Single Sign On`}</StylishText>
|
||||||
title={t`Not enabled`}
|
</Accordion.Control>
|
||||||
color='yellow'
|
<Accordion.Panel>
|
||||||
>
|
{sso_enabled() ? (
|
||||||
<Trans>Single Sign On is not enabled for this server </Trans>
|
<ProviderSection auth_config={auth_config} />
|
||||||
</Alert>
|
) : (
|
||||||
)}
|
<Alert
|
||||||
<Title order={5}>
|
icon={<IconAlertCircle size='1rem' />}
|
||||||
<Trans>Multifactor authentication</Trans>
|
title={t`Not enabled`}
|
||||||
</Title>
|
color='yellow'
|
||||||
<MfaSection />
|
>
|
||||||
<Title order={5}>
|
<Trans>Single Sign On is not enabled for this server </Trans>
|
||||||
<Trans>Access Tokens</Trans>
|
</Alert>
|
||||||
</Title>
|
)}
|
||||||
<TokenSection />
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
<Accordion.Item value='mfa'>
|
||||||
|
<Accordion.Control>
|
||||||
|
<StylishText size='lg'>{t`Multi-Factor Authentication`}</StylishText>
|
||||||
|
</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<MfaSection />
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
<Accordion.Item value='token'>
|
||||||
|
<Accordion.Control>
|
||||||
|
<StylishText size='lg'>{t`Access Tokens`}</StylishText>
|
||||||
|
</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<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,
|
refetch();
|
||||||
action,
|
})
|
||||||
vals
|
.catch((err) => {
|
||||||
).then(() => {
|
hideNotification('email-error');
|
||||||
refetch();
|
|
||||||
});
|
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={selectedEmail}
|
||||||
value={value}
|
onChange={setSelectedEmail}
|
||||||
onChange={setValue}
|
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:`}
|
>
|
||||||
>
|
<Stack mt='xs'>
|
||||||
<Stack mt='xs'>
|
{data.map((email: any) => (
|
||||||
{data.map((email: any) => (
|
<Radio
|
||||||
<Radio
|
key={email.email}
|
||||||
key={email.email}
|
value={String(email.email)}
|
||||||
value={String(email.email)}
|
label={
|
||||||
label={
|
<Group justify='space-apart'>
|
||||||
<Group justify='space-between'>
|
{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>
|
<Group>
|
||||||
)}
|
<Button
|
||||||
</Grid.Col>
|
onClick={() =>
|
||||||
<Grid.Col span={6}>
|
runServerAction('patch', {
|
||||||
<Stack>
|
email: selectedEmail,
|
||||||
<Text>
|
primary: true
|
||||||
<Trans>Add Email Address</Trans>
|
})
|
||||||
</Text>
|
}
|
||||||
<TextInput
|
disabled={!selectedEmail}
|
||||||
label={t`E-Mail`}
|
>
|
||||||
placeholder={t`E-Mail address`}
|
<Trans>Make Primary</Trans>
|
||||||
leftSection={<IconAt />}
|
</Button>
|
||||||
value={newEmailValue}
|
<Button
|
||||||
onChange={(event) => setNewEmailValue(event.currentTarget.value)}
|
onClick={() => runServerAction('put')}
|
||||||
/>
|
disabled={!selectedEmail}
|
||||||
</Stack>
|
>
|
||||||
</Grid.Col>
|
<Trans>Re-send Verification</Trans>
|
||||||
<Grid.Col span={6}>
|
</Button>
|
||||||
<Group>
|
<Button
|
||||||
<Button
|
onClick={() => runServerAction('delete')}
|
||||||
onClick={() =>
|
disabled={!selectedEmail}
|
||||||
runServerAction('post', { email: value, primary: true })
|
color='red'
|
||||||
}
|
>
|
||||||
disabled={emailAvailable}
|
<Trans>Remove</Trans>
|
||||||
>
|
</Button>
|
||||||
<Trans>Make Primary</Trans>
|
</Group>
|
||||||
</Button>
|
</Stack>
|
||||||
<Button
|
</Radio.Group>
|
||||||
onClick={() => runServerAction('put')}
|
)}
|
||||||
disabled={emailAvailable}
|
<Stack>
|
||||||
>
|
<StylishText size='md'>{t`Add Email Address`}</StylishText>
|
||||||
<Trans>Re-send Verification</Trans>
|
<TextInput
|
||||||
</Button>
|
label={t`E-Mail`}
|
||||||
<Button
|
placeholder={t`E-Mail address`}
|
||||||
onClick={() => runServerAction('delete')}
|
leftSection={<IconAt />}
|
||||||
disabled={emailAvailable}
|
aria-label='email-address-input'
|
||||||
>
|
value={newEmailValue}
|
||||||
<Trans>Remove</Trans>
|
onChange={(event) => setNewEmailValue(event.currentTarget.value)}
|
||||||
</Button>
|
/>
|
||||||
</Group>
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col span={6}>
|
|
||||||
<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,61 +421,61 @@ 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
|
||||||
<Alert icon={<IconAlertCircle size='1rem' />} color='yellow'>
|
title={t`Not Configured`}
|
||||||
<Trans>No factors configured</Trans>
|
icon={<IconAlertCircle size='1rem' />}
|
||||||
</Alert>
|
color='yellow'
|
||||||
) : (
|
|
||||||
<Table stickyHeader striped highlightOnHover withTableBorder>
|
|
||||||
<Table.Thead>
|
|
||||||
<Table.Tr>
|
|
||||||
<Table.Th>
|
|
||||||
<Trans>Type</Trans>
|
|
||||||
</Table.Th>
|
|
||||||
<Table.Th>
|
|
||||||
<Trans>Last used at</Trans>
|
|
||||||
</Table.Th>
|
|
||||||
<Table.Th>
|
|
||||||
<Trans>Created at</Trans>
|
|
||||||
</Table.Th>
|
|
||||||
<Table.Th>
|
|
||||||
<Trans>Actions</Trans>
|
|
||||||
</Table.Th>
|
|
||||||
</Table.Tr>
|
|
||||||
</Table.Thead>
|
|
||||||
<Table.Tbody>{rows}</Table.Tbody>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col span={6}>
|
|
||||||
<MfaAddSection
|
|
||||||
usedFactors={usedFactors}
|
|
||||||
refetch={refetch}
|
|
||||||
showRecoveryCodes={showRecoveryCodes}
|
|
||||||
/>
|
|
||||||
<Modal
|
|
||||||
opened={recoveryCodesOpen}
|
|
||||||
onClose={() => {
|
|
||||||
refetch();
|
|
||||||
closeRecoveryCodes();
|
|
||||||
}}
|
|
||||||
title={t`Recovery Codes`}
|
|
||||||
centered
|
|
||||||
>
|
>
|
||||||
<Title order={3}>
|
<Trans>No multi-factor tokens configured for this account</Trans>
|
||||||
<Trans>Unused Codes</Trans>
|
</Alert>
|
||||||
</Title>
|
) : (
|
||||||
<Code>{recoveryCodes?.unused_codes?.join('\n')}</Code>
|
<Table stickyHeader striped highlightOnHover withTableBorder>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>
|
||||||
|
<Trans>Type</Trans>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Trans>Last used at</Trans>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Trans>Created at</Trans>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Trans>Actions</Trans>
|
||||||
|
</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>{rows}</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
<MfaAddSection
|
||||||
|
usedFactors={usedFactors}
|
||||||
|
refetch={refetch}
|
||||||
|
showRecoveryCodes={showRecoveryCodes}
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
opened={recoveryCodesOpen}
|
||||||
|
onClose={() => {
|
||||||
|
refetch();
|
||||||
|
closeRecoveryCodes();
|
||||||
|
}}
|
||||||
|
title={t`Recovery Codes`}
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Title order={3}>
|
||||||
|
<Trans>Unused Codes</Trans>
|
||||||
|
</Title>
|
||||||
|
<Code>{recoveryCodes?.unused_codes?.join('\n')}</Code>
|
||||||
|
|
||||||
<Title order={3}>
|
<Title order={3}>
|
||||||
<Trans>Used Codes</Trans>
|
<Trans>Used Codes</Trans>
|
||||||
</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) => (
|
||||||
<Button
|
<Tooltip label={factor.description} key={factor.type}>
|
||||||
key={factor.type}
|
<Button
|
||||||
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}
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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');
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user