Add E2E test environment variables and update E2E tests

This commit is contained in:
Simon Larsen
2024-04-25 21:53:51 +01:00
parent 9a78c4c24d
commit 11d580a373
24 changed files with 106 additions and 18 deletions

View File

@@ -69,6 +69,7 @@ const LoginPage: () => JSX.Element = () => {
title: 'Email',
fieldType: FormFieldSchemaType.Email,
required: true,
dataTestId: 'email',
},
{
field: {
@@ -85,6 +86,7 @@ const LoginPage: () => JSX.Element = () => {
url: new Route('/accounts/forgot-password'),
openLinkInNewTab: false,
},
dataTestId: 'password',
},
]}
createOrUpdateApiUrl={apiUrl}

View File

@@ -104,6 +104,7 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
disabled: Boolean(initialValues && initialValues['email']),
title: 'Email',
dataTestId: 'email',
},
{
field: {
@@ -113,6 +114,7 @@ const RegisterPage: () => JSX.Element = () => {
placeholder: 'Jeff Smith',
required: true,
title: 'Full Name',
dataTestId: 'name',
},
];
@@ -126,6 +128,7 @@ const RegisterPage: () => JSX.Element = () => {
placeholder: 'Acme, Inc.',
required: true,
title: 'Company Name',
dataTestId: 'companyName',
},
]);
@@ -139,6 +142,7 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
placeholder: '+11234567890',
title: 'Phone Number',
dataTestId: 'companyPhoneNumber',
});
}
}
@@ -155,6 +159,7 @@ const RegisterPage: () => JSX.Element = () => {
placeholder: 'Password',
title: 'Password',
required: true,
dataTestId: 'password',
},
{
field: {
@@ -170,6 +175,7 @@ const RegisterPage: () => JSX.Element = () => {
overrideFieldKey: 'confirmPassword',
required: true,
showEvenIfPermissionDoesNotExist: true,
dataTestId: 'confirmPassword',
},
]);

View File

@@ -14,6 +14,7 @@ export interface CategoryProps {
onChange: (value: Array<CategoryCheckboxValue>) => void;
initialValue?: undefined | Array<CategoryCheckboxValue>;
isLastCategory: boolean;
dataTestId?: string | undefined;
}
enum CategoryCheckboxValueState {
@@ -67,6 +68,7 @@ const Category: FunctionComponent<CategoryProps> = (
<div>
<CheckboxElement
title={props.category.title}
dataTestId={props.dataTestId}
value={
categoryCheckboxState ===
CategoryCheckboxValueState.Checked

View File

@@ -18,6 +18,7 @@ export interface CategoryCheckboxProps
onChange: (value: Array<CategoryCheckboxValue>) => void;
initialValue?: undefined | Array<CategoryCheckboxValue | BaseModel>;
error?: string | undefined;
dataTestId?: string | undefined;
}
const CategoryCheckbox: FunctionComponent<CategoryCheckboxProps> = (
@@ -112,6 +113,7 @@ const CategoryCheckbox: FunctionComponent<CategoryCheckboxProps> = (
return Boolean(option);
}
)}
dataTestId={props.dataTestId}
category={category}
options={options.filter((option: CategoryCheckboxOption) => {
return (option.categoryId || '') === (category?.id || '');

View File

@@ -19,7 +19,7 @@ export interface ComponentProps {
type: CodeType;
onFocus?: (() => void) | undefined;
onBlur?: (() => void) | undefined;
dataTestId?: string;
dataTestId?: string | undefined;
tabIndex?: number | undefined;
error?: string | undefined;
value?: string | undefined;

View File

@@ -31,6 +31,7 @@ export interface ComponentProps {
tabIndex?: number | undefined;
error?: string | undefined;
id?: string | undefined;
dataTestId?: string | undefined;
}
const Dropdown: FunctionComponent<ComponentProps> = (
@@ -144,6 +145,7 @@ const Dropdown: FunctionComponent<ComponentProps> = (
onBlur={() => {
props.onBlur && props.onBlur();
}}
data-testid={props.dataTestId}
tabIndex={props.tabIndex}
isMulti={props.isMultiSelect}
value={value || null}

View File

@@ -28,7 +28,7 @@ export interface ComponentProps {
mimeTypes?: Array<MimeType> | undefined;
onFocus?: (() => void) | undefined;
onBlur?: (() => void) | undefined;
dataTestId?: string;
dataTestId?: string | undefined;
isMultiFilePicker?: boolean | undefined;
tabIndex?: number | undefined;
error?: string | undefined;

View File

@@ -258,6 +258,7 @@ const FormField: <T extends GenericObject>(
? props.error
: undefined
}
dataTestId={props.field.dataTestId}
onChange={async (value: Color | null) => {
props.field.onChange &&
props.field.onChange(value);
@@ -288,6 +289,7 @@ const FormField: <T extends GenericObject>(
}
id={props.field.id}
tabIndex={index}
dataTestId={props.field.dataTestId}
onChange={async (
value:
| DropdownValue
@@ -321,13 +323,13 @@ const FormField: <T extends GenericObject>(
{props.field.fieldType === FormFieldSchemaType.ObjectID && (
<IDGenerator
tabIndex={index}
dataTestId={props.field.dataTestId}
disabled={props.isDisabled || props.field.disabled}
error={
props.touched && props.error
? props.error
: undefined
}
dataTestId={fieldType}
onChange={(value: ObjectID) => {
props.field.onChange &&
props.field.onChange(value);
@@ -355,6 +357,7 @@ const FormField: <T extends GenericObject>(
? props.error
: undefined
}
dataTestId={props.field.dataTestId}
onChange={async (value: string) => {
props.field.onChange &&
props.field.onChange(value);
@@ -381,6 +384,7 @@ const FormField: <T extends GenericObject>(
: undefined
}
tabIndex={index}
dataTestId={props.field.dataTestId}
onChange={async (value: string) => {
props.field.onChange &&
props.field.onChange(value);
@@ -410,6 +414,7 @@ const FormField: <T extends GenericObject>(
}
type={CodeType.JSON}
tabIndex={index}
dataTestId={props.field.dataTestId}
onChange={async (value: string) => {
props.field.onChange &&
props.field.onChange(value);
@@ -445,6 +450,7 @@ const FormField: <T extends GenericObject>(
? props.error
: undefined
}
dataTestId={props.field.dataTestId}
tabIndex={index}
type={CodeType.Markdown}
onChange={async (value: string) => {
@@ -515,6 +521,7 @@ const FormField: <T extends GenericObject>(
onBlur={async () => {
props.setFieldTouched(props.fieldName, true);
}}
dataTestId={props.field.dataTestId}
type={codeType}
initialValue={
props.currentValues &&
@@ -583,6 +590,7 @@ const FormField: <T extends GenericObject>(
]
: []
}
dataTestId={props.field.dataTestId}
initialValue={
props.currentValues &&
(props.currentValues as any)[props.fieldName]
@@ -610,6 +618,7 @@ const FormField: <T extends GenericObject>(
onBlur={async () => {
props.setFieldTouched(props.fieldName, true);
}}
dataTestId={props.field.dataTestId}
initialValue={
props.currentValues &&
(props.currentValues as any)[props.fieldName] &&
@@ -641,6 +650,7 @@ const FormField: <T extends GenericObject>(
props.field.onChange(value);
props.setFieldValue(props.fieldName, value);
}}
dataTestId={props.field.dataTestId}
onBlur={async () => {
props.setFieldTouched(props.fieldName, true);
}}
@@ -679,6 +689,7 @@ const FormField: <T extends GenericObject>(
? props.error
: undefined
}
dataTestId={props.field.dataTestId}
onChange={async (
value: Array<CategoryCheckboxValue>
) => {
@@ -727,7 +738,7 @@ const FormField: <T extends GenericObject>(
? props.error
: undefined
}
dataTestId={fieldType}
dataTestId={props.field.dataTestId}
type={fieldType as InputType}
onChange={(value: string) => {
props.field.onChange &&

View File

@@ -90,4 +90,5 @@ export default interface Field<TEntity> {
props: CustomElementProps
) => ReactElement | undefined; // custom element to render instead of the elements in the form.
categoryCheckboxProps?: CategoryCheckboxProps | undefined; // props for the category checkbox component. If fieldType is CategoryCheckbox, this prop is required.
dataTestId?: string | undefined;
}

View File

@@ -12,6 +12,7 @@ export interface RadioButton {
value: string;
sideTitle?: string | undefined;
sideDescription?: string | undefined;
}
export interface ComponentProps {
@@ -19,6 +20,7 @@ export interface ComponentProps {
initialValue?: string | undefined;
options: Array<RadioButton>;
error?: string | undefined;
dataTestId?: string | undefined;
}
const RadioButtons: FunctionComponent<ComponentProps> = (
@@ -45,7 +47,7 @@ const RadioButtons: FunctionComponent<ComponentProps> = (
return (
<div>
<fieldset role="radiogroup">
<fieldset role="radiogroup" data-testid={props.dataTestId}>
<div className="space-y-2 mt-2">
{props.options &&
props.options.map(

View File

@@ -18,6 +18,7 @@ export interface ComponentProps {
tabIndex?: number | undefined;
error?: string | undefined;
autoFocus?: boolean | undefined;
dataTestId?: string | undefined;
}
const TextArea: FunctionComponent<ComponentProps> = (
@@ -58,6 +59,7 @@ const TextArea: FunctionComponent<ComponentProps> = (
<textarea
autoFocus={props.autoFocus}
placeholder={props.placeholder}
data-testid={props.dataTestId}
className={`${className || ''}`}
value={text}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {

View File

@@ -15,6 +15,7 @@ export interface ComponentProps {
title?: string | undefined;
description?: string | undefined;
error?: string | undefined;
dataTestId?: string | undefined;
}
const Toggle: FunctionComponent<ComponentProps> = (
@@ -76,6 +77,7 @@ const Toggle: FunctionComponent<ComponentProps> = (
props.onFocus();
}
}}
data-testid={props.dataTestId}
onBlur={() => {
if (props.onBlur) {
props.onBlur();

View File

@@ -35,6 +35,7 @@ const Welcome: FunctionComponent<ComponentProps> = (
onClick={() => {
props.onClickShowProjectModal();
}}
dataTestId='create-new-project-button'
/>
}
/>

View File

@@ -13,3 +13,8 @@ export const HTTP_PROTOCOL: Protocol =
env('HTTP_PROTOCOL') === 'https' ? Protocol.HTTPS : Protocol.HTTP;
export const BASE_URL: URL = URL.fromString(`${HTTP_PROTOCOL}${HOST}`);
export const IS_USER_REGISTERED: boolean = env('E2E_TEST_IS_USER_REGISTERED') === 'true';
export const REGISTERED_USER_EMAIL: string = env('E2E_TEST_REGISTERED_USER_EMAIL') || '';
export const REGISTERED_USER_PASSWORD: string = env('E2E_TEST_REGISTERED_USER_PASSWORD') || '';

View File

View File

@@ -0,0 +1,37 @@
import { test, expect } from '@playwright/test';
import { BASE_URL, IS_USER_REGISTERED } from '../../Config';
import Faker from 'Common/Utils/Faker';
import URL from 'Common/Types/API/URL';
test.describe('Account Registration', () => {
test('should register a new account', async ({ page }) => {
if(IS_USER_REGISTERED) {
// pass this test if user is already registered
return;
}
await page.goto(URL.fromString(BASE_URL.toString()).addRoute('/accounts/register').toString());
await page.getByTestId('email').click();
await page.getByTestId('email').fill(Faker.generateEmail().toString());
await page.getByTestId('email').press('Tab');
await page.getByTestId('name').fill('sample');
await page.getByTestId('name').press('Tab');
await page.getByTestId('companyName').fill('sample');
await page.getByTestId('companyName').press('Tab');
await page.getByTestId('companyPhoneNumber').fill('+15853641376');
await page.getByTestId('companyPhoneNumber').press('Tab');
await page.getByTestId('password').fill('sample');
await page.getByTestId('password').press('Tab');
await page.getByTestId('confirmPassword').fill('sample');
await page.getByTestId('Sign Up').click();
// wait for navigation with base url
await page.waitForURL(URL.fromString(BASE_URL.toString()).addRoute('/dashboard/welcome').toString());
expect(page.url()).toBe(URL.fromString(BASE_URL.toString()).addRoute('/dashboard/welcome').toString());
await page.getByTestId('create-new-project-button').click();
});
})

View File

@@ -1,23 +1,24 @@
import { test, expect } from '@playwright/test';
import { BASE_URL } from '../../Config';
import URL from 'Common/Types/API/URL';
test.describe('check live and health check of the app', () => {
test('check if app status is ok', async ({ page }) => {
await page.goto(`${BASE_URL.addRoute("/status").toString()}`);
await page.goto(`${URL.fromString(BASE_URL.toString()).addRoute("/status").toString()}`);
const content = await page.content();
expect(content).toContain('{"status":"ok"}');
});
test('check if app is ready', async ({ page }) => {
await page.goto(`${BASE_URL.addRoute("/status/ready").toString()}`);
await page.goto(`${URL.fromString(BASE_URL.toString()).addRoute("/status/ready").toString()}`);
const content = await page.content();
expect(content).toContain('{"status":"ok"}');
});
test('check if app is live', async ({ page }) => {
await page.goto(`${BASE_URL.addRoute("/status/live").toString()}`);
await page.goto(`${URL.fromString(BASE_URL.toString()).addRoute("/status/live").toString()}`);
const content = await page.content();
expect(content).toContain('{"status":"ok"}');
});

View File

@@ -1,8 +1,9 @@
import { test, expect, Page } from '@playwright/test';
import { BASE_URL } from '../../Config';
import URL from 'Common/Types/API/URL';
test.beforeEach(async ({ page }: { page: Page }) => {
await page.goto(BASE_URL.toString());
await page.goto(URL.fromString(BASE_URL.toString()).toString());
});
test.describe('check if pages loades with its title', () => {
test('has title', async ({ page }: { page: Page }) => {
@@ -19,6 +20,6 @@ test.describe('check if pages loades with its title', () => {
.getByRole('link', { name: 'OneUptime', exact: true })
.click();
await expect(page).toHaveURL(BASE_URL.toString());
await expect(page).toHaveURL(URL.fromString(BASE_URL.toString()).toString());
});
});

View File

@@ -1,8 +1,9 @@
import { test, expect, Page } from '@playwright/test';
import { BASE_URL } from '../../Config';
import URL from 'Common/Types/API/URL';
test.beforeEach(async ({ page }: { page: Page }) => {
await page.goto(BASE_URL.toString());
await page.goto(URL.fromString(BASE_URL.toString()).toString());
});
test.describe('navigation bar', () => {

View File

@@ -1,7 +1,8 @@
import { test, expect, Page } from '@playwright/test';
import { BASE_URL } from '../../Config';
import URL from 'Common/Types/API/URL';
test.beforeEach(async ({ page }: { page: Page }) => {
await page.goto(BASE_URL.toString());
await page.goto(URL.fromString(BASE_URL.toString()).toString());
});
test('sign in button ', async ({ page }: { page: Page }) => {
await page.getByRole('link', { name: 'Sign in' }).click();

View File

@@ -1,12 +1,13 @@
import { test, expect, Page } from '@playwright/test';
import { BASE_URL } from '../../Config';
import URL from 'Common/Types/API/URL';
test.beforeEach(async ({ page }: { page: Page }) => {
await page.goto(BASE_URL.toString());
await page.goto(URL.fromString(BASE_URL.toString()).toString());
});
test('sign up button', async ({ page }: { page: Page }) => {
await page.getByTestId('Sign-up').click();
await expect(page).toHaveURL(
BASE_URL.addRoute('/accounts/register').toString()
URL.fromString(BASE_URL.toString()).addRoute('/accounts/register').toString()
);
});

View File

@@ -1,23 +1,23 @@
import { test, expect } from '@playwright/test';
import { BASE_URL } from '../../Config';
import URL from 'Common/Types/API/URL';
test.describe('check live and health check of the app', () => {
test('check if app status is ok', async ({ page }) => {
await page.goto(`${BASE_URL.addRoute("/ingestor/status").toString()}`);
await page.goto(`${URL.fromString(BASE_URL.toString()).addRoute("/ingestor/status").toString()}`);
const content = await page.content();
expect(content).toContain('{"status":"ok"}');
});
test('check if app is ready', async ({ page }) => {
await page.goto(`${BASE_URL.addRoute("/ingestor/status/ready").toString()}`);
await page.goto(`${URL.fromString(BASE_URL.toString()).addRoute("/ingestor/status/ready").toString()}`);
const content = await page.content();
expect(content).toContain('{"status":"ok"}');
});
test('check if app is live', async ({ page }) => {
await page.goto(`${BASE_URL.addRoute("/ingestor/status/live").toString()}`);
await page.goto(`${URL.fromString(BASE_URL.toString()).addRoute("/ingestor/status/live").toString()}`);
const content = await page.content();
expect(content).toContain('{"status":"ok"}');
});

View File

@@ -197,4 +197,7 @@ cronJobs:
enabled: false
e2e:
enabled: false
isUserRegistered: false
registeredUserEmail:
registeredUserPassword:

View File

@@ -193,5 +193,10 @@ ADMIN_DASHBOARD_OPENTELEMETRY_EXPORTER_OTLP_HEADERS=
LOG_LEVEL=ERROR
# Thse env vars are for E2E tests
E2E_TEST_IS_USER_REGISTERED=false
E2E_TEST_USER_EMAIL=
E2E_TEST_USER_PASSWORD=