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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT=locationId
LOCATION_INTERNAL=locationId
LOCATION_INTERNAL_TWO=locationId
LOCATION_WARD=locationId
LOCATION_CC_DEPOT=locationId
PRODUCT_ONE=productId
PRODUCT_TWO=productId
PRODUCT_THREE=productId
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ jobs:
LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT: ${{ secrets.LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT }}
LOCATION_INTERNAL: ${{ secrets.LOCATION_INTERNAL }}
LOCATION_INTERNAL_TWO: ${{ secrets.LOCATION_INTERNAL_TWO }}
LOCATION_CC_DEPOT: ${{ secrets.LOCATION_CC_DEPOT }}
PRODUCT_ONE: ${{ secrets.PRODUCT_ONE }}
PRODUCT_TWO: ${{ secrets.PRODUCT_TWO }}
PRODUCT_THREE: ${{ secrets.PRODUCT_THREE }}
Expand Down
30 changes: 28 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ export default defineConfig({
forbidOnly: !!appConfig.isCI,
/* Retry on CI only */
retries: appConfig.isCI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: 1,
/* Total worker pool. Each project below caps itself at 1 worker,
* so this just lets the cycleCount project run on its own worker
* alongside the rest of the suite instead of queuing behind it. */
workers: 2,
Comment thread
kkrawczyk123 marked this conversation as resolved.
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
Expand Down Expand Up @@ -57,6 +59,12 @@ export default defineConfig({
testDir: './src/setup',
dependencies: ['validate-data-setup'],
},
{
name: 'auth-setup-cycleCount',
testMatch: 'authCycleCount.setup.ts',
testDir: './src/setup',
dependencies: ['auth-setup'],
},
{
name: 'create-data-setup',
testMatch: 'createData.setup.ts',
Expand Down Expand Up @@ -86,6 +94,8 @@ export default defineConfig({
},
{
name: 'chromium',
testIgnore: '**/cycleCount/**',
workers: 1,
use: {
...devices['Desktop Chrome'],
viewport: { width: 1366, height: 768 },
Expand All @@ -98,5 +108,21 @@ export default defineConfig({
'validate-clean-state',
],
},
{
name: 'chromium-cycleCount',
testDir: './src/tests/cycleCount',
workers: 1,
use: {
...devices['Desktop Chrome'],
viewport: { width: 1366, height: 768 },
storageState: appConfig.users['main'].ccStoragePath,
},
dependencies: [
'auth-setup-cycleCount',
'create-data-setup',
'data-import-setup',
'validate-clean-state',
],
},
],
});
21 changes: 21 additions & 0 deletions src/api/CycleCountService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import BaseServiceModel from '@/api/BaseServiceModel';
import { CYCLE_COUNT_BY_ID } from '@/constants/apiUrls';

class CycleCountService extends BaseServiceModel {
/**
Deletes a cycle count and cascades to its cycle count request, counted
transactions and their sources, reverting the quantityOnHand adjustments
they caused.
*/
async deleteCycleCount(
facilityId: string,
cycleCountId: string
): Promise<boolean> {
const apiResponse = await this.request.delete(
CYCLE_COUNT_BY_ID(facilityId, cycleCountId)
);
return apiResponse.ok();
}
}

export default CycleCountService;
42 changes: 42 additions & 0 deletions src/api/LocationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
LOCATION_BY_ID,
LOCATION_TYPES,
} from '@/constants/apiUrls';
import { LocationTypeCode } from '@/constants/LocationTypeCode';
import {
ApiResponse,
CreateLocationPayload,
Expand Down Expand Up @@ -88,6 +89,47 @@ class LocationService extends BaseServiceModel {
throw new Error('Problem fetching location types');
}
}

/**
Returns the id of the bin location with the given name under the given
parent location, or undefined if it doesn't exist.
*/
async getBinLocation(
name: string,
parentLocationId: string
): Promise<string | undefined> {
const { data: existingLocations } = await this.searchInternalLocations(
name,
parentLocationId
);
const existingLocation = existingLocations.find(
(location) => location.name === name
);
return existingLocation?.id;
}

/**
Creates a bin location with the given name under the given parent
location and returns its id.
*/
async createBinLocation(
name: string,
parentLocationId: string
): Promise<string> {
const { data: locationTypes } = await this.getLocationTypes();
const binLocationType = locationTypes.find(
(locationType) =>
locationType.locationTypeCode === LocationTypeCode.BIN_LOCATION
);

const { data: createdLocation } = await this.createLocation({
active: true,
name,
locationType: binLocationType,
parentLocation: { id: parentLocationId },
});
return createdLocation.id;
}
}

export default LocationService;
6 changes: 6 additions & 0 deletions src/components/Navbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class Navbar extends BasePageModel {
.filter({ visible: true });
}

getSectionNavItem(sectionName: string, itemName: string) {
return this.getSectionTitle(sectionName)
.locator('..')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's that?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNavItem(name) (line 26-28) searches for a menu item across the entire navbar. That's fine when item names are unique, but it breaks for names that also exist elsewhere — like "Reporting" appears both as a link inside the "Cycle Count" dropdown section and as a completely separate top-level "Reporting" module in the navbar. getNavItem('Reporting') would match both and throw a strict-mode violation.

.getByRole('menuitem', { name: itemName, exact: true });
}

get editProfileButton() {
return this.navbar.getByRole('menuitem', { name: 'Edit Profile' });
}
Expand Down
27 changes: 27 additions & 0 deletions src/config/AppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export enum LOCATION_KEY {
NO_PICK_AND_PUTAWAY_STOCK = 'noPickAndPutawayStockDepot',
BIN_LOCATION = 'internalLocation',
BIN_LOCATION2 = 'internalLocation2',
CC_DEPOT = 'ccDepot',
}

export enum PRODUCT_KEY {
Expand Down Expand Up @@ -71,6 +72,11 @@ class AppConfig {
'/inventory.csv'
);

public static CYCLE_COUNT_INVENTORY_IMPORT_FILE_PATH = path.join(
AppConfig.DATA_IMPORT_DIRECTORY_PATH,
'/cycleCountInventory.csv'
);

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

Expand Down Expand Up @@ -309,6 +315,27 @@ class AppConfig {
type: LocationTypeCode.BIN_LOCATION,
parentLocation: env.get('LOCATION_MAIN').required().asString(),
}),
ccDepot: new LocationConfig({
key: LOCATION_KEY.CC_DEPOT,
id: env.get('LOCATION_CC_DEPOT').required().asString(),
requiredActivityCodes: new Set([
ActivityCode.MANAGE_INVENTORY,
ActivityCode.SUBMIT_REQUEST,
ActivityCode.SEND_STOCK,
ActivityCode.PLACE_REQUEST,
ActivityCode.PLACE_ORDER,
ActivityCode.FULFILL_REQUEST,
ActivityCode.EXTERNAL,
ActivityCode.RECEIVE_STOCK,
ActivityCode.PARTIAL_RECEIVING,
ActivityCode.PICK_STOCK,
ActivityCode.PUTAWAY_STOCK,
ActivityCode.CONSUME_STOCK,
ActivityCode.ADJUST_INVENTORY,
]),
type: LocationTypeCode.DEPOT,
required: true,
}),
};

// Fulfill products data in app config dynamically based on the products.csv
Expand Down
5 changes: 5 additions & 0 deletions src/config/TestUserConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class TestUserConfig {
username: string;
password: string;
storagePath: string;
ccStoragePath: string;
requiredRoles: Set<RoleType>;

constructor({
Expand All @@ -35,6 +36,10 @@ class TestUserConfig {
AppConfig.AUTH_STORAGE_DIR_PATH,
storageFileName
);
this.ccStoragePath = path.join(
AppConfig.AUTH_STORAGE_DIR_PATH,
storageFileName.replace('.json', '-CC.json')
);
this.requiredRoles = requiredRoles;
}

Expand Down
4 changes: 4 additions & 0 deletions src/constants/DateFormats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum DateFormat {
DISPLAY = 'DD/MMM/YYYY',
DEFAULT = 'MM/DD/YYYY',
}
4 changes: 4 additions & 0 deletions src/constants/apiUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,7 @@ export const PRODUCT_IMPORT = `${PRODUCT_API}/import`;
// INVENTORY
export const INVENTORY_IMPORT = (facilityId: string) =>
`${API}/facilities/${facilityId}/inventories/import`;

// CYCLE COUNT
export const CYCLE_COUNT_BY_ID = (facilityId: string, cycleCountId: string) =>
`${API}/facilities/${facilityId}/cycle-counts/${cycleCountId}`;
5 changes: 5 additions & 0 deletions src/constants/applicationUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const INVENTORY_URL = {
`${INVENTORY_URL.base}/deleteTransaction/${id}`,
};

const CYCLE_COUNT_URL = {
base: './inventory/cycleCount',
};

const INVENTORY_ITEM_URL = {
base: './inventoryItem',
showStockCard: (id: string) => `${INVENTORY_ITEM_URL.base}/showStockCard/${id}`,
Expand Down Expand Up @@ -91,6 +95,7 @@ const ORDER_URL = {

export {
AUTH_URL,
CYCLE_COUNT_URL,
DASHBOARD_URL,
INVENTORY_ITEM_URL,
INVENTORY_URL,
Expand Down
25 changes: 25 additions & 0 deletions src/fixtures/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BrowserContext, test as baseTest } from '@playwright/test';

import AuthService from '@/api/AuthService';
import CycleCountService from '@/api/CycleCountService';
import GenericService from '@/api/GenericService';
import LocationService from '@/api/LocationService';
import PutawayService from '@/api/PutawayService';
Expand All @@ -21,6 +22,11 @@ import CreateLocationGroupPage from '@/pages/locationGroup/CreateLocationGroupPa
import EditLocationGroupPage from '@/pages/locationGroup/EditLocationGroupPage';
import LocationGroupsListPage from '@/pages/locationGroup/LocationGroupsListPage';
import LoginPage from '@/pages/LoginPage';
import ConfirmToCountStepPage from '@/pages/manageCycleCount/ConfirmToCountStepPage';
import ConfirmToRecountStepPage from '@/pages/manageCycleCount/ConfirmToRecountStepPage';
import CountStepPage from '@/pages/manageCycleCount/CountStepPage';
import ManageCycleCountPage from '@/pages/manageCycleCount/ManageCycleCountPage';
import RecountStepPage from '@/pages/manageCycleCount/RecountStepPage';
import CreateOrganizationPage from '@/pages/oranization/CreateOrganizationPage';
import EditOrganizationPage from '@/pages/oranization/EditOrganizationPage';
import OrganizationListPage from '@/pages/oranization/OrganizationListPage';
Expand Down Expand Up @@ -77,6 +83,11 @@ type Fixtures = {
productEditPage: ProductEditPage;
editTransactionPage: EditTransactionPage;
addCommentToPutawayPage: AddCommentToPutawayPage;
manageCycleCountPage: ManageCycleCountPage;
countStepPage: CountStepPage;
confirmToCountStepPage: ConfirmToCountStepPage;
recountStepPage: RecountStepPage;
confirmToRecountStepPage: ConfirmToRecountStepPage;
// COMPONENTS
navbar: Navbar;
locationChooser: LocationChooser;
Expand All @@ -89,6 +100,7 @@ type Fixtures = {
receivingService: ReceivingService;
putawayService: PutawayService;
transactionService: TransactionService;
cycleCountService: CycleCountService;
// LOCATIONS DATA
mainLocationService: LocationData;
noManageInventoryDepotService: LocationData;
Expand All @@ -99,6 +111,7 @@ type Fixtures = {
noPickAndPutawayStockDepotService: LocationData;
internalLocationService: LocationData;
internalLocation2Service: LocationData;
ccDepotService: LocationData;

// PRODUCT DATA
productService: ProductData;
Expand Down Expand Up @@ -159,6 +172,14 @@ export const test = baseTest.extend<Fixtures>({
use(new EditTransactionPage(page)),
addCommentToPutawayPage: async ({ page }, use) =>
use(new AddCommentToPutawayPage(page)),
manageCycleCountPage: async ({ page }, use) =>
use(new ManageCycleCountPage(page)),
countStepPage: async ({ page }, use) => use(new CountStepPage(page)),
confirmToCountStepPage: async ({ page }, use) =>
use(new ConfirmToCountStepPage(page)),
recountStepPage: async ({ page }, use) => use(new RecountStepPage(page)),
confirmToRecountStepPage: async ({ page }, use) =>
use(new ConfirmToRecountStepPage(page)),
// COMPONENTS
navbar: async ({ page }, use) => use(new Navbar(page)),
locationChooser: async ({ page }, use) => use(new LocationChooser(page)),
Expand All @@ -177,6 +198,8 @@ export const test = baseTest.extend<Fixtures>({
use(new PutawayService(page.request)),
transactionService: async ({ page }, use) =>
use(new TransactionService(page.request)),
cycleCountService: async ({ page }, use) =>
use(new CycleCountService(page.request)),
// LOCATIONS
mainLocationService: async ({ page }, use) =>
use(new LocationData(LOCATION_KEY.MAIN, page.request)),
Expand All @@ -196,6 +219,8 @@ export const test = baseTest.extend<Fixtures>({
use(new LocationData(LOCATION_KEY.BIN_LOCATION, page.request)),
internalLocation2Service: async ({ page }, use) =>
use(new LocationData(LOCATION_KEY.BIN_LOCATION2, page.request)),
ccDepotService: async ({ page }, use) =>
use(new LocationData(LOCATION_KEY.CC_DEPOT, page.request)),
// PRODUCTS
productService: async ({ page }, use) => use(new ProductData(page.request)),
// USERS
Expand Down
3 changes: 2 additions & 1 deletion src/pages/inbound/create/components/AddItemsTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import _ from 'lodash';
import DatePicker from '@/components/DatePicker';
import Select from '@/components/Select';
import TextField from '@/components/TextField';
import { DateFormat } from '@/constants/DateFormats';
import BasePageModel from '@/pages/BasePageModel';
import { CreateInboundAddItemsTableEntity } from '@/types';
import { formatDate } from '@/utils/DateUtils';
Expand Down Expand Up @@ -116,7 +117,7 @@ class Row extends BasePageModel {
if (!_.isNil(rowValues.expirationDate)) {
await test.step('Assert value in expiry date field', async () => {
await expect(this.expirationDate.textbox).toHaveValue(
formatDate(rowValues.expirationDate as Date, 'DD/MMM/YYYY')
formatDate(rowValues.expirationDate as Date, DateFormat.DISPLAY)
);
});
}
Expand Down
Loading
Loading