Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default defineConfig({

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'retain-on-failure',

launchOptions: {
// slowMo: 1000,
},
Expand Down Expand Up @@ -60,14 +60,23 @@ export default defineConfig({
storageState: appConfig.users['main'].storagePath,
},
},
{
name: 'data-import-setup',
testMatch: 'dataImport.setup.ts',
testDir: './src/setup',
dependencies: ['create-data-setup'],
use: {
storageState: appConfig.users['main'].storagePath,
},
},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1366, height: 768 },
storageState: appConfig.users['main'].storagePath,
},
dependencies: ['auth-setup', 'create-data-setup'],
dependencies: ['auth-setup', 'create-data-setup', 'data-import-setup'],
},
],
});
23 changes: 23 additions & 0 deletions src/api/InventoryService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import BaseServiceModel from '@/api/BaseServiceModel';
import { jsonToCsv } from '@/utils/ServiceUtils';

class InventoryService extends BaseServiceModel {
async importInventories(data: Record<string, string>[], facilityId: string): Promise<void> {
try {
const csvContent = jsonToCsv(data);

const response = await this.request.post(`./api/facilities/${facilityId}/inventories/import`, {
data: csvContent,
headers: { 'Content-Type': 'text/csv' },
});

if (!response.ok()) {
throw new Error(`Import failed with status ${response.status()}: ${await response.text()}`);
}
} catch (error) {
throw new Error(`Problem importing inventories: ${error instanceof Error ? error.message : String(error)}`);
}
}
}

export default InventoryService;
20 changes: 19 additions & 1 deletion src/api/ProductService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BaseServiceModel from '@/api/BaseServiceModel';
import { ApiResponse, ProductDemandResponse, ProductResponse } from '@/types';
import { parseRequestToJSON } from '@/utils/ServiceUtils';
import { jsonToCsv, parseRequestToJSON } from '@/utils/ServiceUtils';

class ProductService extends BaseServiceModel {
async getDemand(id: string): Promise<ApiResponse<ProductDemandResponse>> {
Expand All @@ -22,6 +22,24 @@ class ProductService extends BaseServiceModel {
throw new Error('Problem fetching product data');
}
}

async importProducts(data: Record<string, string>[]): Promise<ApiResponse<ProductResponse[]>> {
try {
const csvContent = jsonToCsv(data);

const apiResponse = await this.request.post(
'./api/products/import',
{
data: csvContent,
headers: { 'Content-Type': 'text/csv' }
}
);

return await parseRequestToJSON(apiResponse);
} catch (error) {
throw new Error(`Problem importing products: ${error instanceof Error ? error.message : String(error)}`);
}
}
}

export default ProductService;
55 changes: 17 additions & 38 deletions src/config/AppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import TestUserConfig from '@/config/TestUserConfig';
import { ActivityCode } from '@/constants/ActivityCodes';
import { LocationTypeCode } from '@/constants/LocationTypeCode';
import RoleType from '@/constants/RoleTypes';
import { readCsvFile } from '@/utils/FileIOUtils';
import UniqueIdentifier from '@/utils/UniqueIdentifier';

export enum USER_KEY {
Expand Down Expand Up @@ -52,6 +53,12 @@ class AppConfig {

public static TEST_DATA_FILE_PATH = path.join(process.cwd(), '.data.json');

public static DATA_IMPORT_DIRECTORY_PATH = path.join(process.cwd(), 'src/setup/dataImport');

public static PRODUCTS_IMPORT_FILE_PATH = path.join(AppConfig.DATA_IMPORT_DIRECTORY_PATH, '/products.csv');

public static INVENTORY_IMPORT_FILE_PATH = path.join(AppConfig.DATA_IMPORT_DIRECTORY_PATH, '/inventory.csv');

// Base URL to use in actions like `await page.goto('./dashboard')`.
public appURL!: string;

Expand All @@ -65,7 +72,7 @@ class AppConfig {
public locations!: Record<LOCATION_KEY, LocationConfig>;

// test products used in all of the tests
public products!: Record<PRODUCT_KEY, ProductConfig>;
public products: Record<string, ProductConfig> = {};

//recivingbin configurable prefix
public receivingBinPrefix!: string;
Expand Down Expand Up @@ -258,44 +265,16 @@ class AppConfig {
}),
};

this.products = {
productOne: new ProductConfig({
id: env.get('PRODUCT_ONE').asString(),
key: PRODUCT_KEY.ONE,
name: this.uniqueIdentifier.generateUniqueString('product-one'),
quantity: 122,
required: false,
}),
productTwo: new ProductConfig({
id: env.get('PRODUCT_TWO').asString(),
key: PRODUCT_KEY.TWO,
name: this.uniqueIdentifier.generateUniqueString('product-two'),
quantity: 123,
required: false,
}),
productThree: new ProductConfig({
id: env.get('PRODUCT_THREE').asString(),
key: PRODUCT_KEY.THREE,
name: this.uniqueIdentifier.generateUniqueString('product-three'),
quantity: 150,
// Fulfill products data in app config dynamically based on the products.csv
const productsData = readCsvFile(AppConfig.PRODUCTS_IMPORT_FILE_PATH);
productsData.forEach((productData) => {
this.products[productData['ProductCode']] = new ProductConfig({
key: productData['ProductCode'],
name: productData['Name'],
quantity: parseInt(productData['Quantity']),
required: false,
}),
productFour: new ProductConfig({
id: env.get('PRODUCT_FOUR').asString(),
key: PRODUCT_KEY.FOUR,
name: this.uniqueIdentifier.generateUniqueString('product-four'),
quantity: 100,
required: false,
}),
productFive: new ProductConfig({
id: env.get('PRODUCT_FIVE').asString(),
key: PRODUCT_KEY.FIVE,
name: this.uniqueIdentifier.generateUniqueString('aa-product-five'),
//'aa' part was added to improve visibility of ordering products alphabetically
quantity: 160,
required: false,
}),
};
})
})

this.receivingBinPrefix = env
.get('RECEIVING_BIN_PREFIX')
Expand Down
2 changes: 1 addition & 1 deletion src/config/ProductConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class ProductConfig {
* @returns {boolean}
*/
get isCreateNew() {
return !this.id;
return !this.readId();
}

/**
Expand Down
19 changes: 3 additions & 16 deletions src/fixtures/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import LocationChooser from '@/components/LocationChooser';
import Navbar from '@/components/Navbar';
import AppConfig, {
LOCATION_KEY,
PRODUCT_KEY,
USER_KEY,
} from '@/config/AppConfig';
import CreateInbound from '@/pages/inbound/create/CreateInboundPage';
Expand Down Expand Up @@ -82,11 +81,7 @@ type Fixtures = {
wardLocationService: LocationData;
noPickAndPutawayStockDepotService: LocationData;
// PRODUCT DATA
mainProductService: ProductData;
otherProductService: ProductData;
thirdProductService: ProductData;
fourthProductService: ProductData;
fifthProductService: ProductData;
productService: ProductData;
// USERS DATA
mainUserService: UserData;
altUserService: UserData;
Expand Down Expand Up @@ -159,16 +154,8 @@ export const test = baseTest.extend<Fixtures>({
noPickAndPutawayStockDepotService: async ({ page }, use) =>
use(new LocationData(LOCATION_KEY.NO_PICK_AND_PUTAWAY_STOCK, page.request)),
// PRODUCTS
mainProductService: async ({ page }, use) =>
use(new ProductData(PRODUCT_KEY.ONE, page.request)),
otherProductService: async ({ page }, use) =>
use(new ProductData(PRODUCT_KEY.TWO, page.request)),
thirdProductService: async ({ page }, use) =>
use(new ProductData(PRODUCT_KEY.THREE, page.request)),
fourthProductService: async ({ page }, use) =>
use(new ProductData(PRODUCT_KEY.FOUR, page.request)),
fifthProductService: async ({ page }, use) =>
use(new ProductData(PRODUCT_KEY.FIVE, page.request)),
productService: async ({ page }, use) =>
use(new ProductData(page.request)),
// USERS
mainUserService: async ({ page }, use) =>
use(new UserData(USER_KEY.MAIN, page.request)),
Expand Down
41 changes: 1 addition & 40 deletions src/setup/createData.setup.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,19 @@
import AppConfig from '@/config/AppConfig';
import { test } from '@/fixtures/fixtures';
import { readFile, writeToFile } from '@/utils/FileIOUtils';
import { parseUrl } from '@/utils/UrlUtils';

test('create data', async ({
page,
createProductPage,
productShowPage,
locationService,
mainLocationService,
}) => {
// eslint-disable-next-line playwright/no-conditional-in-test
const data = readFile(AppConfig.TEST_DATA_FILE_PATH) || {};

const seedData: Record<'products' | 'locations', Record<string, string>> = {
const seedData: Record<'locations', Record<string, string>> = {
...data,
products: {},
locations: {},
};

// // PRODUCST
const products = Object.values(AppConfig.instance.products).filter(
(product) => product.isCreateNew
);

for (const product of products) {
await test.step(`create product ${product.key}`, async () => {
await createProductPage.goToPage();
await createProductPage.waitForUrl();
await createProductPage.productDetails.nameField.fill(product.name);
await createProductPage.productDetails.categorySelect.click();
await createProductPage.productDetails.categorySelectDropdown
.getByRole('listitem')
.first()
.click();
await createProductPage.saveButton.click();

await productShowPage.recordStockButton.click();

await productShowPage.recordStock.lineItemsTable
.row(1)
.newQuantity.getByRole('textbox')
.fill(`${product.quantity}`);
await productShowPage.recordStock.lineItemsTable.saveButton.click();
await productShowPage.showStockCardButton.click();

const productUrl = parseUrl(
page.url(),
'/openboxes/inventoryItem/showStockCard/$id'
);
seedData.products[`${product.key}`] = productUrl.id;
});
}

// LOCATIONS
const { organization } = await mainLocationService.getLocation();
const { data: locationTypes } = await locationService.getLocationTypes();
Expand Down
41 changes: 41 additions & 0 deletions src/setup/dataImport.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import InventoryService from '@/api/InventoryService';
import ProductService from '@/api/ProductService';
import AppConfig from '@/config/AppConfig';
import { test } from '@/fixtures/fixtures';
import { readCsvFile, readFile, writeToFile } from '@/utils/FileIOUtils';

test('import data', async ({ request }) => {
// eslint-disable-next-line playwright/no-conditional-in-test
const data = readFile(AppConfig.TEST_DATA_FILE_PATH) || {};

const seedData: Record<'products', Record<string, string>> = {
...data,
products: {},
};

// PRODUCTS
const productService = new ProductService(request);

const productsData = readCsvFile(AppConfig.PRODUCTS_IMPORT_FILE_PATH);

await test.step(`importing ${productsData.length} products`, async () => {
const importedData = await productService.importProducts(productsData);
importedData.data.forEach((product) => {
seedData.products[product.productCode] = product.id;
})
})

// INVENTORIES
const inventoryService = new InventoryService(request);

const inventoriesData = readCsvFile(AppConfig.INVENTORY_IMPORT_FILE_PATH);

await test.step(`importing ${inventoriesData.length} inventories`, async () => {
await inventoryService.importInventories(
inventoriesData,
AppConfig.instance.locations.main.id,
);
});

writeToFile(AppConfig.TEST_DATA_FILE_PATH, seedData);
})
3 changes: 3 additions & 0 deletions src/setup/dataImport/inventory.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Product code,Product,Lot number,Expiration date,Bin location,Quantity,Comment
1,E2E-product-one,,,,122,
2,E2E-product-two,,,,123,
6 changes: 6 additions & 0 deletions src/setup/dataImport/products.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Id,ProductCode,ProductType,Name,ProductFamily,Category,GLAccount,Description,UnitOfMeasure,Tags,UnitCost,LotAndExpiryControl,ColdChain,ControlledSubstance,HazardousMaterial,Reconditioned,Manufacturer,BrandName,ManufacturerCode,ManufacturerName,Vendor,VendorCode,VendorName,UPC,NDC,Created,Updated
,1,Default,E2E-product-one,,ARVS,,,,,,,,,,,,,,,,,,,,,
,2,Default,E2E-product-two,,ARVS,,,,,,,,,,,,,,,,,,,,,
,3,Default,E2E-product-three,,ARVS,,,,,,,,,,,,,,,,,,,,,
,4,Default,E2E-product-four,,ARVS,,,,,,,,,,,,,,,,,,,,,
,5,Default,E2E-product-five,,ARVS,,,,,,,,,,,,,,,,,,,,,
18 changes: 10 additions & 8 deletions src/tests/inbound/createInbound/createInbound.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ test.describe('Create inbound stock movement', () => {

test.beforeEach(
async ({
mainProductService,
otherProductService,
productService,
mainUserService,
supplierLocationService,
mainLocationService,
}) => {
const PRODUCT_ONE = await mainProductService.getProduct();
const PRODUCT_TWO = await otherProductService.getProduct();
productService.setProduct('1');
const PRODUCT_ONE = await productService.getProduct();
productService.setProduct('2');
const PRODUCT_TWO = await productService.getProduct();
USER = await mainUserService.getUser();
ORIGIN = await supplierLocationService.getLocation();
CURRENT_LOCATION = await mainLocationService.getLocation();
Expand Down Expand Up @@ -188,15 +189,16 @@ test.describe('Values persistance between steps', () => {

test.beforeEach(
async ({
mainProductService,
otherProductService,
productService,
mainUserService,
createInboundPage,
mainLocationService,
supplierLocationService,
}) => {
const PRODUCT_ONE = await mainProductService.getProduct();
const PRODUCT_TWO = await otherProductService.getProduct();
productService.setProduct('1');
const PRODUCT_ONE = await productService.getProduct();
productService.setProduct('2');
const PRODUCT_TWO = await productService.getProduct();
USER = await mainUserService.getUser();
CURRENT_LOCATION = await mainLocationService.getLocation();
ORIGIN = await supplierLocationService.getLocation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ test.describe('Download documents from inbound send page', () => {

test.beforeEach(
async ({
mainProductService,
productService,
mainUserService,
supplierLocationService,
}) => {
const PRODUCT_ONE = await mainProductService.getProduct();
productService.setProduct('1');
const PRODUCT_ONE = await productService.getProduct();
USER = await mainUserService.getUser();
ORIGIN = await supplierLocationService.getLocation();

Expand Down
Loading
Loading