diff --git a/artifacts/accessibilityReport.html b/artifacts/accessibilityReport.html index d112a48c831..e924f8add49 100644 --- a/artifacts/accessibilityReport.html +++ b/artifacts/accessibilityReport.html @@ -87,7 +87,7 @@

-
axe-core found 14 violations
+
axe-core found 4 violations
@@ -106,34 +106,10 @@
axe-core found 14 violations
- - - - - - - - - - - - - - - - - - - - - - - - - + @@ -196,7 +172,7 @@

Element location

#root > .TitleAnnouncer_HiddenTitleAnnouncer__IxWhC[aria-live="polite"]

Element source

-
<div id="title-announcer" aria-live="polite" class="TitleAnnouncer_HiddenTitleAnnouncer__IxWhC" style="">my.move.mil - Move - Fda5473a F6ac 486f 83a4 D894da44836d</div>
+
<div id="title-announcer" aria-live="polite" class="TitleAnnouncer_HiddenTitleAnnouncer__IxWhC" style="">my.move.mil - Move review</div>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
duplicate-id WCAG 2 Level A, WCAG 4.1.1 minor8
2Heading levels should only increase by oneheading-orderBest practicemoderate 1
3Links must be distinguishable without relying on colorlink-in-text-blockWCAG 2 Level A, WCAG 1.4.1serious1
4Page should contain a level-one headingpage-has-heading-oneBest practicemoderate1
52 All page content should be contained by landmarks region Best practice
@@ -209,360 +185,6 @@
#app-root > .TitleAnnouncer_HiddenTitleAnnouncer__IxWhC[aria-live="polite"]
2 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
-

Element source

-
<filter id="filter-1"><feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix></filter>
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: filter-1
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
-
3 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
-

Element source

-
<g id="circle-checked" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: circle-checked
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
-
4 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
-

Element source

-
<g id="Checklist_check" transform="translate(1.000000, 1.000000)">
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: Checklist_check
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
-
5 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
-

Element source

-
<circle id="Oval" fill="#162E51" cx="65.5" cy="65.5" r="65.5"></circle>
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: Oval
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
-
6 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
-

Element source

-
<g id="Group" transform="translate(32.750000, 32.750000)" stroke-linecap="round" stroke-linejoin="round">
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: Group
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
-
7 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
-

Element source

-
<g filter="url(#filter-1)" id="icon-/-check-copy-2"><g><polyline id="Path" stroke="#000000" stroke-width="8.25" points="55.0950521 21.7480469 27.1850586 49.2955729 14.4986979 36.7739702"></polyline></g></g>
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: icon-/-check-copy-2
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
-
8 -

Element location

-
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
-

Element source

-
<polyline id="Path" stroke="#000000" stroke-width="8.25" points="55.0950521 21.7480469 27.1850586 49.2955729 14.4986979 36.7739702"></polyline>
-
-
-

Fix any of the following:

-
    -
  • Document has multiple static elements with the same id attribute: Path
  • -
-
-

Related node:

-
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
-
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
-
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
-
- - - -
-
-
-
- 2. Heading levels should only increase by one -
- Learn more -
-
-
heading-order
-
- Best practice -
-
-
-

Ensures the order of headings is semantically correct

-
- moderate -
-
-
-
- Issue Tags: - cat.semantics - - best-practice -
-
-
- - - - - - - - - - - - - - - -
#Issue Description - To solve this violation, you need to... -
1 -

Element location

-
h6
-

Element source

-
<h6 class="Contact_contactHeader__v48ze">Contacts</h6>
-
-
-

Fix any of the following:

-
    -
  • Heading order invalid
  • -
-
-
-
-
-
-
-
-
-
- 3. Links must be distinguishable without relying on color -
- Learn more -
-
-
link-in-text-block
-
- WCAG 2 Level A, WCAG 1.4.1 -
-
-
-

Ensure links are distinguished from surrounding text in a way that does not rely on color

-
- serious -
-
-
-
- Issue Tags: - cat.color - - wcag2a - - wcag141 -
-
-
- - - - - - - - - - - - - - - -
#Issue Description - To solve this violation, you need to... -
1 -

Element location

-
p:nth-child(3) > .usa-link[target="_blank"][rel="noopener noreferrer"]
-

Element source

-
<a class="usa-link" target="_blank" rel="noopener noreferrer" href="https://www.militaryonesource.mil/moving-housing/moving/planning-your-move/customer-service-contacts-for-military-pcs/">directory of PCS-related contacts</a>
-
-
-

Fix any of the following:

-
    -
  • The link has insufficient color contrast of 2.57:1 with the surrounding text. (Minimum contrast is 3:1, link text: #0050d8, surrounding text: #1b1b1b)
  • -
  • The link has no styling (such as underline) to distinguish it from the surrounding text
  • -
-
-

Related node:

-
.Contact_contactContainer__jYtTz > p:nth-child(3)
-
.Contact_contactContainer__jYtTz > p:nth-child(3)
-
-
-
-
-
-
-
-
- 4. Page should contain a level-one heading -
- Learn more -
-
-
page-has-heading-one
-
- Best practice -
-
-
-

Ensure that the page, or at least one of its frames contains a level-one heading

-
- moderate -
-
-
-
- Issue Tags: - cat.semantics - - best-practice -
-
-
- - - - - - - - - - - - - -
#Issue Description - To solve this violation, you need to... -
1 -

Element location

-
html
-

Element source

-
<html lang="en">
-
-
-

Fix all of the following:

-
    -
  • Page must have a level-one heading
  • -
-
-
@@ -572,7 +194,7 @@
- 5. All page content should be contained by landmarks + 2. All page content should be contained by landmarks
{ test.skip(multiMoveEnabled === 'true', 'Skip if MultiMove workflow is enabled.'); @@ -111,7 +112,6 @@ test.describe('HHG', () => { test.describe('(MultiMove) HHG', () => { test.skip(multiMoveEnabled === 'false', 'Skip if MultiMove workflow is not enabled.'); - test('A customer can create, edit, and delete an HHG shipment', async ({ page, customerPage }) => { // Generate a new onboarded user with orders and log in const move = await customerPage.testHarness.buildMoveWithOrders(); @@ -217,4 +217,82 @@ test.describe('(MultiMove) HHG', () => { await expect(page.getByText('The shipment was deleted.')).toBeVisible(); await expect(page.getByTestId('stepContainer3').getByText('Set up shipments')).toBeVisible(); }); + + test.skip(alaskaFF === 'false', 'Skip if the create customer & AK FFs are not enabled.'); + test('A customer can create, edit, and submit an international Alaska HHG shipment', async ({ + page, + customerPage, + }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate from MM Dashboard to Move + await customerPage.navigateFromMMDashboardToMove(move); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an HHG shipment + await page.getByText('Movers pack and ship it, paid by the government').click(); + await customerPage.navigateForward(); + + // Fill in form to create HHG shipment + await customerPage.waitForPage.hhgShipment(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByTestId('remarks').fill('Going to Alaska'); + await customerPage.navigateForward(); + + // Verify that form submitted, initial setup has it being a domestic HHG shipment (dHHG) + await customerPage.waitForPage.reviewShipments(); + await expect(page.getByText('dHHG')).toBeVisible(); + await expect(page.getByText('Going to Alaska')).toBeVisible(); + await expect(page.getByTestId('ShipmentContainer').getByText('123 Any Street')).toBeVisible(); + + // Navigate to edit shipment from the review page + await page.getByTestId('edit-shipment-btn').click(); + await customerPage.waitForPage.hhgShipment(); + + // Update form (adding pickup and delivery address) + const pickupLocation = 'LAWTON, OK 73505 (COMANCHE)'; + const pickupAddress = page.getByRole('group', { name: 'Pickup Address' }); + await pickupAddress.getByLabel('Address 1').fill('123 Warm St.'); + await page.locator('input[id="pickup.address-location-input"]').fill('73505'); + await expect(page.getByText(pickupLocation, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + + // Delivery address + const deliveryLocation = 'JBER, AK 99505 (ANCHORAGE)'; + const deliveryAddress = page.getByRole('group', { name: 'Delivery Address' }); + await deliveryAddress.getByText('Yes').nth(0).click(); + await deliveryAddress.getByLabel('Address 1').nth(0).fill('123 Cold Ave.'); + await page.locator('input[id="delivery.address-location-input"]').fill('99505'); + await expect(page.getByText(deliveryLocation, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + await customerPage.navigateForward(); + + // Verify that shipment updated - should now be an iHHG shipment + await customerPage.waitForPage.reviewShipments(); + await expect(page.getByText('iHHG')).toBeVisible(); + await expect(page.getByTestId('ShipmentContainer').getByText('123 Warm St.')).toBeVisible(); + await expect(page.getByTestId('ShipmentContainer').getByText('123 Cold Ave.')).toBeVisible(); + + await page.getByRole('button', { name: 'Next' }).click(); + await expect(page).toHaveURL(/\/moves\/[^/]+\/agreement/); + await expect(page.getByRole('heading', { name: 'Now for the official part…' })).toBeVisible(); + + await page.locator('input[name="signature"]').fill('Mister Alaska'); + await expect(page.getByRole('button', { name: 'Complete' })).toBeEnabled(); + await page.getByRole('button', { name: 'Complete' }).click(); + await expect(page.getByText('submitted your move request.')).toBeVisible(); + }); }); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 5aa4db714c3..34564b6a181 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -291,61 +291,6 @@ test.describe('Services counselor user', () => { await scPage.navigateToMove(move.locator); }); - /** - * This test is being temporarily skipped until flakiness issues - * can be resolved. It was skipped in cypress and is not part of - * the initial playwright conversion. - ahobson 2023-01-12 - */ - test.skip('is able to edit allowances', () => { - // // TOO Moves queue - // cy.wait(['@getSortedMoves']); - // await expect(page.getByText(moveLocator).click()).toBeVisible(); - // cy.url().should('include', `/moves/${moveLocator}/details`); - // // Move Details page - // cy.watest(['@getMoves', '@getOrders', '@getMTOShipments', async ({page}) => { - // // Navigate to Edit allowances page - // await expect(page.locator('[data-testid="edit-allowances"]')).toContainText('Edit allowances').click(); - // // Toggle between Edit Allowances and Edit Orders page - // await page.locator('[data-testid="view-orders"]').click(); - // cy.url().should('include', `/moves/${moveLocator}/orders`); - // await page.locator('[data-testid="view-allowances"]').click(); - // cy.url().should('include', `/moves/${moveLocator}/allowances`); - // cy.watest(['@getMoves', async ({page}) => { - // await page.locator('form').within(($form) => { - // // Edit pro-gear, pro-gear spouse, RME, SIT, and OCIE fields - // await page.locator('input[name="proGearWeight"]').fill('1999'); - // await page.locator('input[name="proGearWeightSpouse"]').fill('499'); - // await page.locator('input[name="requiredMedicalEquipmentWeight"]').fill('999'); - // await page.locator('input[name="storageInTransit"]').fill('199'); - // await page.locator('input[name="organizationalClothingAndIndividualEquipment"]').siblings('label[for="ocieInput"]').click(); - // // Edit grade and authorized weight - // await expect(page.locator('select[name=agency]')).toContainText('Army'); - // await page.locator('select[name=agency]').selectOption({ label: 'Navy'}); - // await expect(page.locator('select[name="grade"]')).toContainText('E-1'); - // await page.locator('select[name="grade"]').selectOption({ label: 'W-2'}); - // //Edit DependentsAuthorized - // await page.locator('input[name="dependentsAuthorized"]').siblings('label[for="dependentsAuthorizedInput"]').click(); - // // Edit allowances page | Save - // await expect(page.locator('[data-testid="scAllowancesSave"]')).toBeEnabled().click(); - // }); - // cy.wait(['@patchAllowances']); - // // Verify edited values are saved - // cy.url().should('include', `/moves/${moveLocator}/details`); - // cy.watest(['@getMoves', '@getOrders', '@getMTOShipments', async ({page}) => { - // await expect(page.locator('[data-testid="progear"]')).toContainText('1,999'); - // await expect(page.locator('[data-testid="spouseProgear"]')).toContainText('499'); - // await expect(page.locator('[data-testid="rme"]')).toContainText('999'); - // await expect(page.locator('[data-testid="storageInTransit"]')).toContainText('199'); - // await expect(page.locator('[data-testid="ocie"]')).toContainText('Unauthorized'); - // await expect(page.locator('[data-testid="branchGrade"]')).toContainText('Navy'); - // await expect(page.locator('[data-testid="branchGrade"]')).toContainText('W-2'); - // await expect(page.locator('[data-testid="dependents"]')).toContainText('Unauthorized'); - // // Edit allowances page | Cancel - // await expect(page.locator('[data-testid="edit-allowances"]')).toContainText('Edit allowances').click(); - // await expect(page.locator('button')).toContainText('Cancel').click(); - // cy.url().should('include', `/moves/${moveLocator}/details`); - }); - test('is able to see and use the left navigation', async ({ page }) => { await expect(page.locator('a[href*="#shipments"]')).toContainText('Shipments'); await expect(page.locator('a[href*="#orders"]')).toContainText('Orders'); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingInternational.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingInternational.spec.js new file mode 100644 index 00000000000..ca6f289bd44 --- /dev/null +++ b/playwright/tests/office/servicescounseling/servicesCounselingInternational.spec.js @@ -0,0 +1,114 @@ +import { DEPARTMENT_INDICATOR_OPTIONS } from '../../utils/office/officeTest'; + +import { test, expect } from './servicesCounselingTestFixture'; + +const createCustomerFF = process.env.FEATURE_FLAG_COUNSELOR_MOVE_CREATE; +const alaskaFF = process.env.FEATURE_FLAG_ENABLE_ALASKA; +const LocationLookup = 'BEVERLY HILLS, CA 90210 (LOS ANGELES)'; + +test.describe('Services counselor user', () => { + test.describe('Can create a customer with an international Alaska move', () => { + test.beforeEach(async ({ scPage }) => { + await scPage.signInAsNewServicesCounselorUser(); + }); + + test.skip( + createCustomerFF === 'false' || alaskaFF === 'false', + 'Skip if the create customer & AK FFs are not enabled.', + ); + test('create a customer and add a basic iHHG shipment with Alaska address', async ({ page, officePage }) => { + // make sure we see the queue + await expect(page.getByText('Moves')).toBeVisible(); + await expect(page.getByRole('link', { name: 'Counseling' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Customer Search' })).toBeVisible(); + + // we need to search before we have access to the create customer button + await page.getByRole('link', { name: 'Customer Search' }).click(); + await page.getByText('Customer Name').click(); + await page.getByLabel('Search').fill('Test'); + await page.getByRole('button', { name: 'Search' }).click(); + await expect(page.getByRole('button', { name: 'Add Customer' })).toBeEnabled(); + await page.getByRole('button', { name: 'Add Customer' }).click(); + + // fill out the customer form + await page.getByRole('combobox', { name: 'Branch of service' }).selectOption({ label: 'Army' }); + await page.getByLabel('DoD ID number').fill('1234567890'); + await page.getByLabel('First name').fill('Mister'); + await page.getByLabel('Last name').fill('Alaska'); + await page.getByLabel('Best contact phone').fill('555-555-5555'); + await page.getByLabel('Personal email').fill('alaskaBoi@mail.mil'); + await page.getByText('Phone', { exact: true }).nth(0).click(); + await page.getByLabel('Address 1').nth(0).fill('1234 Pickup St.'); + await page.getByLabel('Location Lookup').nth(0).fill('90210'); + await expect(page.getByText(LocationLookup, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + await page.getByLabel('Address 1').nth(1).fill('1234 Backup St.'); + await page.getByLabel('Location Lookup').nth(1).fill('90210'); + await expect(page.getByText(LocationLookup, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + await page.getByLabel('Name', { exact: true }).fill('Backup Friend'); + await page.getByLabel('Email', { exact: true }).nth(1).fill('backupFriend@mail.mil'); + await page.getByLabel('Phone', { exact: true }).nth(1).fill('555-867-5309'); + await page.locator('label[for="noCreateOktaAccount"]').click(); + await page.locator('label[for="yesCacUser"]').click(); + await page.keyboard.press('Tab'); + await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled(); + await page.getByRole('button', { name: 'Save' }).click(); + + // fill out the orders form + await page.getByLabel('Orders type').selectOption({ label: 'Permanent Change Of Station (PCS)' }); + await page.getByLabel('Orders date').fill('12/25/2024'); + await page.getByLabel('Orders date').blur(); + await page.getByLabel('Report by date').fill('1/25/2025'); + await page.getByLabel('Report by date').blur(); + const originLocation = 'Tinker AFB, OK 73145'; + await page.getByLabel('Current duty location').fill('Tinker'); + await expect(page.getByText(originLocation, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + const pickupLocation = 'Elmendorf AFB, AK 99506'; + await page.getByLabel('New duty location').fill('JBER'); + await expect(page.getByText(pickupLocation, { exact: true })).toBeVisible(); + await page.keyboard.press('Enter'); + await page.locator('label[for="hasDependentsNo"]').click(); + await page.getByLabel('Pay grade').selectOption({ label: 'E-7' }); + await expect(page.getByRole('button', { name: 'Next' })).toBeEnabled(); + await page.getByRole('button', { name: 'Next' }).click(); + + // now we need to add order data + await page.getByRole('link', { name: 'View and edit orders' }).click(); + const filepondContainer = page.locator('.filepond--wrapper'); + await officePage.uploadFileViaFilepond(filepondContainer, 'AF Orders Sample.pdf'); + await expect(page.getByText('Uploading')).toBeVisible(); + await expect(page.getByText('Uploading')).not.toBeVisible(); + await expect(page.getByText('Upload complete')).not.toBeVisible(); + await expect(page.getByTestId('uploads-table').getByText('AF Orders Sample.pdf')).toBeVisible(); + await page.getByRole('button', { name: 'Done' }).click(); + await page.getByLabel('Department indicator').selectOption(DEPARTMENT_INDICATOR_OPTIONS.ARMY); + await page.getByLabel('Orders number').fill('123456'); + await page.getByLabel('Orders type detail').selectOption('Shipment of HHG Permitted'); + await page.getByLabel('TAC', { exact: true }).nth(0).fill('TEST'); + await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled(); + await page.getByRole('button', { name: 'Save' }).click(); + + // adding an HHG shipment + await page.getByLabel('Add a new shipment').selectOption('HHG'); + await expect(page.getByText('Add shipment details')).toBeVisible(); + await expect(page.getByText('Weight allowance: 11,000 lbs')).toBeVisible(); + await page.getByLabel('Requested pickup date').fill('25 Dec 2024'); + await page.getByLabel('Requested pickup date').blur(); + await page.getByText('Use pickup address').click(); + await page.getByLabel('Requested delivery date').fill('25 Dec 2022'); + await page.getByLabel('Requested delivery date').blur(); + await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled(); + await page.getByRole('button', { name: 'Save' }).click(); + + // verify we can see the iHHG shipment, submit it to the TOO + await expect(page.getByText('iHHG')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Submit move details' })).toBeEnabled(); + await page.getByRole('button', { name: 'Submit move details' }).click(); + await expect(page.getByText('Are you sure?')).toBeVisible(); + await page.getByRole('button', { name: 'Yes, submit' }).click(); + await expect(page.getByText('Move submitted.')).toBeVisible(); + }); + }); +}); diff --git a/playwright/tests/office/txo/tioFlowsInternational.spec.js b/playwright/tests/office/txo/tioFlowsInternational.spec.js new file mode 100644 index 00000000000..2b97f19078b --- /dev/null +++ b/playwright/tests/office/txo/tioFlowsInternational.spec.js @@ -0,0 +1,190 @@ +import { test, expect, OfficePage } from '../../utils/office/officeTest'; + +/** + * TioFlowPage test fixture + * + * The logic in TioFlowPage is only used in this file, so keep the + * playwright test fixture in this file. + * @extends OfficePage + */ +class TioFlowPage extends OfficePage { + /** + * @param {OfficePage} officePage + * @param {Object} move + * @param {Boolean} usePaymentRequest + * @override + */ + constructor(officePage, move, usePaymentRequest) { + super(officePage.page, officePage.request); + this.move = move; + this.moveLocator = move.locator; + if (usePaymentRequest !== false) { + this.paymentRequest = this.findPaymentRequestBySequenceNumber(1); + } + } + + /** + * @param {number} sequenceNumber + * @returns {object} + */ + findPaymentRequestBySequenceNumber(sequenceNumber) { + return this.move.PaymentRequests.find((pr) => pr.sequence_number === sequenceNumber); + } + + /** + * Complete the service item card + * @param {import('@playwright/test').Locator} serviceItemCardLocator + * @param {boolean} approve + */ + async completeServiceItemCard(serviceItemCardLocator, approve = false) { + // serviceItemAmount + if (!approve) { + const inputEl = serviceItemCardLocator.locator('input[data-testid="rejectRadio"]'); + const id = await inputEl.getAttribute('id'); + await this.page.locator(`label[for="${id}"]`).click(); + await this.page.locator('textarea[data-testid="rejectionReason"]').fill('This is not a valid request'); + } else { + const inputEl = serviceItemCardLocator.locator('input[data-testid="approveRadio"]'); + const id = await inputEl.getAttribute('id'); + await this.page.locator(`label[for="${id}"]`).click(); + } + await this.slowDown(); + } + + /** + * approve the service item + */ + async approveServiceItem() { + await this.completeServiceItem(true); + } + + /** + * complete the service item, approving or rejecting it + * @param {boolean} approved + */ + async completeServiceItem(approved) { + const cards = this.page.getByTestId('ServiceItemCard'); + const cardCount = await cards.count(); + expect(cardCount).toBeGreaterThan(0); + for (let i = 0; i < cardCount; i += 1) { + await this.completeServiceItemCard(cards.nth(i), approved); + } + } + + async slowDown() { + await this.page.waitForLoadState('networkidle'); + // sleep for 500ms + await new Promise((r) => { + setTimeout(() => r(undefined), 500); + }); + } +} + +const alaskaEnabled = process.env.FEATURE_FLAG_ENABLE_ALASKA; + +test.describe('TIO user', () => { + /** @type {TioFlowPage} */ + let tioFlowPage; + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('can review a payment request for a basic iHHG Alaska move', async ({ page, officePage }) => { + test.slow(); + const move = await officePage.testHarness.buildnternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(); + await officePage.signInAsNewTIOUser(); + + tioFlowPage = new TioFlowPage(officePage, move, true); + await tioFlowPage.waitForLoading(); + await officePage.tioNavigateToMove(tioFlowPage.moveLocator); + await officePage.page.getByRole('heading', { name: 'Payment Requests', exact: true }).waitFor(); + expect(page.url()).toContain('/payment-requests'); + await expect(page.getByTestId('MovePaymentRequests')).toBeVisible(); + + const prNumber = tioFlowPage.paymentRequest.payment_request_number; + const prHeading = page.getByRole('heading', { name: `Payment Request ${prNumber}` }); + await expect(prHeading).toBeVisible(); + await tioFlowPage.waitForLoading(); + + await page.getByRole('button', { name: 'Review service items' }).click(); + + await page.waitForURL(`**/payment-requests/${tioFlowPage.paymentRequest.id}`); + await tioFlowPage.waitForLoading(); + + // there should be four service items - let's approve all of them + await expect(page.getByTestId('ReviewServiceItems')).toBeVisible(); + await expect(page.getByText('International Shipping & Linehaul')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('ISLH price'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + + await expect(page.getByText('International HHG Pack')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('International Pack price'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + + await expect(page.getByText('International HHG Unpack')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('International Unpack price'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + + await expect(page.getByText('International POE Fuel Surcharge')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Mileage'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('ZIP 74133 to Port ZIP 98424'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Mileage factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + + // that should be it + await expect(page.getByText('needs your review')).toHaveCount(0, { timeout: 10000 }); + await page.getByText('Complete request').click(); + + await expect(page.locator('[data-testid="requested"]')).toContainText('$4,281.48'); + await expect(page.locator('[data-testid="accepted"]')).toContainText('$4,281.48'); + await expect(page.locator('[data-testid="rejected"]')).toContainText('$0.00'); + + await page.getByText('Authorize payment').click(); + await tioFlowPage.waitForLoading(); + + await tioFlowPage.slowDown(); + expect(page.url()).toContain('/payment-requests'); + + await expect(page.getByTestId('tag')).toBeVisible(); + await expect(page.getByTestId('tag').getByText('Reviewed')).toHaveCount(1); + + // ensure the payment request we approved no longer has the "Review Service Items" button + await expect(page.getByText('Review Service Items')).toHaveCount(0); + + // Go back to queue + await page.locator('a[title="Home"]').click(); + await tioFlowPage.waitForLoading(); + + // search for the moveLocator in case this move doesn't show up on the first page + await page.locator('#locator').fill(tioFlowPage.moveLocator); + await page.locator('#locator').blur(); + const paymentSection = page.locator(`[data-uuid="${tioFlowPage.paymentRequest.id}"]`); + // the payment request that is now in the "Reviewed" status will no longer appear + // in the TIO queue - only "Payment requested" moves will appear + await expect(paymentSection.locator('td', { hasText: 'Reviewed' })).not.toBeVisible(); + }); +}); diff --git a/playwright/tests/office/txo/tooFlows.spec.js b/playwright/tests/office/txo/tooFlows.spec.js index ebc7a29bfc6..dd62f16bb46 100644 --- a/playwright/tests/office/txo/tooFlows.spec.js +++ b/playwright/tests/office/txo/tooFlows.spec.js @@ -18,8 +18,6 @@ const SearchTerms = ['SITEXT', '8796353598', 'Spacemen']; const StatusFilterOptions = ['Draft', 'New Move', 'Needs Counseling', 'Service counseling completed', 'Move approved']; -const alaskaEnabled = process.env.FEATURE_FLAG_ENABLE_ALASKA; - test.describe('TOO user', () => { /** @type {TooFlowPage} */ let tooFlowPage; @@ -471,65 +469,6 @@ test.describe('TOO user', () => { await expect(cancelAlert).not.toBeVisible(); }); - /** - * This test is being temporarily skipped until flakiness issues - * can be resolved. It was skipped in cypress and is not part of - * the initial playwright conversion. - ahobson 2023-01-10 - */ - test.skip('is able to edit allowances', async ({ page }) => { - // Navigate to Edit allowances page - await expect(page.getByTestId('edit-allowances')).toContainText('Edit allowances'); - await page.getByText('Edit allowances').click(); - - // // Toggle between Edit Allowances and Edit Orders page - // await page.locator('[data-testid="view-orders"]').click(); - // cy.url().should('include', `/moves/${moveLocator}/orders`); - // await page.locator('[data-testid="view-allowances"]').click(); - // cy.url().should('include', `/moves/${moveLocator}/allowances`); - - // await page.locator('form').within(($form) => { - // // Edit pro-gear, pro-gear spouse, RME, SIT, and OCIE fields - // await page.locator('input[name="proGearWeight"]').fill('1999'); - // await page.locator('input[name="proGearWeightSpouse"]').fill('499'); - // await page.locator('input[name="requiredMedicalEquipmentWeight"]').fill('999'); - // await page.locator('input[name="storageInTransit"]').fill('199'); - // await page.locator('input[name="organizationalClothingAndIndividualEquipment"]').siblings('label[for="ocieInput"]').click(); - - // // Edit grade and authorized weight - // await expect(page.locator('select[name=agency]')).toContainText('Army'); - // await page.locator('select[name=agency]').selectOption({ label: 'Navy'}); - // await expect(page.locator('select[name="grade"]')).toContainText('E-1'); - // await page.locator('select[name="grade"]').selectOption({ label: 'W-2'}); - // await page.locator('input[name="authorizedWeight"]').fill('11111'); - - // //Edit DependentsAuthorized - // await page.locator('input[name="dependentsAuthorized"]').siblings('label[for="dependentsAuthorizedInput"]').click(); - - // // Edit allowances page | Save - // await expect(page.locator('button').contains('Save')).toBeEnabled().click(); - - // cy.wait(['@patchAllowances']); - - // // Verify edited values are saved - // cy.url().should('include', `/moves/${moveLocator}/details`); - - // await expect(page.locator('[data-testid="progear"]')).toContainText('1,999'); - // await expect(page.locator('[data-testid="spouseProgear"]')).toContainText('499'); - // await expect(page.locator('[data-testid="rme"]')).toContainText('999'); - // await expect(page.locator('[data-testid="storageInTransit"]')).toContainText('199'); - // await expect(page.locator('[data-testid="ocie"]')).toContainText('Unauthorized'); - - // await expect(page.locator('[data-testid="authorizedWeight"]')).toContainText('11,111'); - // await expect(page.locator('[data-testid="branchGrade"]')).toContainText('Navy'); - // await expect(page.locator('[data-testid="branchGrade"]')).toContainText('W-2'); - // await expect(page.locator('[data-testid="dependents"]')).toContainText('Unauthorized'); - - // // Edit allowances page | Cancel - // await expect(page.locator('[data-testid="edit-allowances"]')).toContainText('Edit allowances').click(); - // await expect(page.locator('button')).toContainText('Cancel').click(); - // cy.url().should('include', `/moves/${moveLocator}/details`); - }); - test('is able to edit shipment', async ({ page }) => { const deliveryDate = new Date().toLocaleDateString('en-US'); const LocationLookup = 'BEVERLY HILLS, CA 90210 (LOS ANGELES)'; @@ -580,126 +519,6 @@ test.describe('TOO user', () => { }); }); - test.describe('with International HHG Moves', () => { - test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); - test('is able to approve and reject international crating/uncrating service items', async ({ - officePage, - page, - }) => { - const move = await officePage.testHarness.buildHHGMoveWithIntlCratingServiceItemsTOO(); - await officePage.signInAsNewTOOUser(); - tooFlowPage = new TooFlowPage(officePage, move); - await tooFlowPage.waitForLoading(); - await officePage.tooNavigateToMove(tooFlowPage.moveLocator); - - // Edit the shipment address to AK - await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); - await page.locator('select[name="delivery.address.state"]').selectOption({ label: 'AK' }); - await page.locator('[data-testid="submitForm"]').click(); - await expect(page.locator('[data-testid="submitForm"]')).not.toBeEnabled(); - await tooFlowPage.waitForPage.moveDetails(); - - await tooFlowPage.waitForLoading(); - await tooFlowPage.approveAllShipments(); - - await page.getByTestId('MoveTaskOrder-Tab').click(); - await tooFlowPage.waitForLoading(); - expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); - - // Wait for page to load to deal with flakiness resulting from Service Item tables loading - await tooFlowPage.page.waitForLoadState(); - - // Move Task Order page - await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); - - /** - * @function - * @description This test approves and rejects service items, which moves them from one table to another - * and expects the counts of each table to increment/decrement by one item each time - * This function gets the service items for a given table to help count them - * @param {import("playwright-core").Locator} table - * @returns {import("playwright-core").Locator} - */ - const getServiceItemsInTable = (table) => { - return table.getByRole('rowgroup').nth(1).getByRole('row'); - }; - - const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); - let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); - const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); - let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); - const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); - let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); - - await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); - await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); - - await expect(page.getByTestId('modal')).not.toBeVisible(); - - // Approve a requested service item - expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); - // ICRT - await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); - await tooFlowPage.waitForLoading(); - - await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); - approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); - - await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); - requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); - - // IUCRT - await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); - await tooFlowPage.waitForLoading(); - - await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); - approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); - - await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); - requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); - - // Reject a requested service item - await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); - expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); - // ICRT - await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); - - await expect(page.getByTestId('modal')).toBeVisible(); - let modal = page.getByTestId('modal'); - - await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); - await modal.getByRole('textbox').fill('my very valid reason'); - await modal.getByRole('button', { name: 'Submit' }).click(); - - await expect(page.getByTestId('modal')).not.toBeVisible(); - - await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); - await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); - rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); - - await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); - requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); - - // IUCRT - await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); - - await expect(page.getByTestId('modal')).toBeVisible(); - modal = page.getByTestId('modal'); - - await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); - await modal.getByRole('textbox').fill('my very valid reason'); - await modal.getByRole('button', { name: 'Submit' }).click(); - - await expect(page.getByTestId('modal')).not.toBeVisible(); - - await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); - await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); - rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); - - await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); - }); - }); - test.describe('with HHG Moves after actual pickup date', () => { test.beforeEach(async ({ officePage }) => { const move = await officePage.testHarness.buildHHGMoveForTOOAfterActualPickupDate(); diff --git a/playwright/tests/office/txo/tooFlowsInternational.spec.js b/playwright/tests/office/txo/tooFlowsInternational.spec.js new file mode 100644 index 00000000000..31fa0741487 --- /dev/null +++ b/playwright/tests/office/txo/tooFlowsInternational.spec.js @@ -0,0 +1,167 @@ +import { test, expect } from '../../utils/office/officeTest'; + +import { TooFlowPage } from './tooTestFixture'; + +const alaskaEnabled = process.env.FEATURE_FLAG_ENABLE_ALASKA; + +test.describe('TOO user', () => { + /** @type {TooFlowPage} */ + let tooFlowPage; + + test.describe('with HHG Moves', () => { + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('is able to approve an AK iHHG shipment that generates 4 basic service items', async ({ + page, + officePage, + }) => { + const move = await officePage.testHarness.buildInternationalAlaskaBasicHHGMoveForTOO(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, move); + await tooFlowPage.waitForLoading(); + await officePage.tooNavigateToMove(tooFlowPage.moveLocator); + await expect(page.locator('#approved-shipments')).not.toBeVisible(); + await expect(page.locator('#requested-shipments')).toBeVisible(); + await expect(page.getByText('Approve selected')).toBeDisabled(); + await expect(page.locator('#approvalConfirmationModal [data-testid="modal"]')).not.toBeVisible(); + + await tooFlowPage.waitForLoading(); + await tooFlowPage.approveAllShipments(); + + // Redirected to Move Task Order page - should have 4 basic iHHG service items + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); + await expect(page.getByTestId('ShipmentContainer')).toBeVisible(); + await expect(page.locator('[data-testid="ApprovedServiceItemsTable"] h3')).toContainText( + 'Approved Service Items (4 items)', + ); + + // Navigate back to Move Details + await page.getByTestId('MoveDetails-Tab').click(); + await tooFlowPage.waitForLoading(); + + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/details`); + await expect(page.locator('#approvalConfirmationModal [data-testid="modal"]')).not.toBeVisible(); + await expect(page.locator('#approved-shipments')).toBeVisible(); + await expect(page.locator('#requested-shipments')).not.toBeVisible(); + await expect(page.getByText('Approve selected')).not.toBeVisible(); + }); + + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('is able to approve and reject international crating/uncrating service items', async ({ + officePage, + page, + }) => { + const move = await officePage.testHarness.buildHHGMoveWithIntlCratingServiceItemsTOO(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, move); + await tooFlowPage.waitForLoading(); + await officePage.tooNavigateToMove(tooFlowPage.moveLocator); + + // Edit the shipment address to AK + await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); + await page.locator('input[id="delivery.address-location-input"]').fill('99505'); + await page.keyboard.press('Enter'); + + await page.getByRole('button', { name: 'Save' }).click(); + await tooFlowPage.waitForPage.moveDetails(); + + await tooFlowPage.waitForLoading(); + await tooFlowPage.approveAllShipments(); + + await page.getByTestId('MoveTaskOrder-Tab').click(); + await tooFlowPage.waitForLoading(); + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); + + // Wait for page to load to deal with flakiness resulting from Service Item tables loading + await tooFlowPage.page.waitForLoadState(); + + // Move Task Order page + await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); + + /** + * @function + * @description This test approves and rejects service items, which moves them from one table to another + * and expects the counts of each table to increment/decrement by one item each time + * This function gets the service items for a given table to help count them + * @param {import("playwright-core").Locator} table + * @returns {import("playwright-core").Locator} + */ + const getServiceItemsInTable = (table) => { + return table.getByRole('rowgroup').nth(1).getByRole('row'); + }; + + const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); + let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); + let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); + let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + // Approve a requested service item + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + // ICRT + await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); + await tooFlowPage.waitForLoading(); + + await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); + approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // IUCRT + await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); + await tooFlowPage.waitForLoading(); + + await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); + approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // Reject a requested service item + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + // ICRT + await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); + + await expect(page.getByTestId('modal')).toBeVisible(); + let modal = page.getByTestId('modal'); + + await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); + await modal.getByRole('textbox').fill('my very valid reason'); + await modal.getByRole('button', { name: 'Submit' }).click(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); + rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // IUCRT + await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); + + await expect(page.getByTestId('modal')).toBeVisible(); + modal = page.getByTestId('modal'); + + await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); + await modal.getByRole('textbox').fill('my very valid reason'); + await modal.getByRole('button', { name: 'Submit' }).click(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); + rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + }); + }); +}); diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index 74feee4ffef..cc84f4c61f9 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -283,6 +283,14 @@ export class TestHarness { return this.buildDefault('HHGMoveWithServiceItemsAndPaymentRequestsAndFilesForTOO'); } + /** + * Use testharness to build hhg move for TOO with Alaska address + * @returns {Promise} + */ + async buildInternationalAlaskaBasicHHGMoveForTOO() { + return this.buildDefault('InternationalAlaskaBasicHHGMoveForTOO'); + } + /** * Use testharness to build hhg move with international crating service items for TOO * @returns {Promise} @@ -371,6 +379,14 @@ export class TestHarness { return this.buildDefault('HHGMoveWithServiceItemsandPaymentRequestsForTIO'); } + /** + * Use testharness to build hhg move for TIO + * @returns {Promise} + */ + async buildnternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO() { + return this.buildDefault('InternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO'); + } + /** * Use testharness to build hhg move for QAE * @returns {Promise} diff --git a/src/components/Office/ServiceItemCalculations/helpers.js b/src/components/Office/ServiceItemCalculations/helpers.js index 6577e9118b8..c5d26d73e75 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.js +++ b/src/components/Office/ServiceItemCalculations/helpers.js @@ -207,6 +207,34 @@ const mileageZip = (params) => { return calculation(value, label, formatDetail(detail)); }; +const mileageZipPOEFSC = (params) => { + const value = `${formatMileage(parseInt(getParamValue(SERVICE_ITEM_PARAM_KEYS.DistanceZip, params), 10))}`; + const label = SERVICE_ITEM_CALCULATION_LABELS.Mileage; + const detail = `${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.ZipPickupAddress]} ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.ZipPickupAddress, + params, + )} to ${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.PortZip]} ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.PortZip, + params, + )}`; + + return calculation(value, label, formatDetail(detail)); +}; + +const mileageZipPODFSC = (params) => { + const value = `${formatMileage(parseInt(getParamValue(SERVICE_ITEM_PARAM_KEYS.DistanceZip, params), 10))}`; + const label = SERVICE_ITEM_CALCULATION_LABELS.Mileage; + const detail = `${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.PortZip]} ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.PortZip, + params, + )} to ${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.ZipDestAddress]} ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.ZipDestAddress, + params, + )}`; + + return calculation(value, label, formatDetail(detail)); +}; + const mileageZipSIT = (params, itemCode) => { let label; let distanceZip; @@ -254,6 +282,12 @@ const mileageZipSIT = (params, itemCode) => { return calculation(value, label, formatDetail(detail)); }; +const internationalShippingAndLineHaulPrice = (params, shipmentType) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.InternationalShippingAndLinehaul; + return calculation(value, label, formatDetail(referenceDate(params, shipmentType)), formatDetail(peak(params))); +}; + const baselineLinehaulPrice = (params, shipmentType) => { const value = getPriceRateOrFactor(params); const label = SERVICE_ITEM_CALCULATION_LABELS.BaselineLinehaulPrice; @@ -457,6 +491,12 @@ const packPrice = (params, shipmentType) => { ); }; +const internationalPackPrice = (params, shipmentType) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.PackPriceInternational; + return calculation(value, label, formatDetail(referenceDate(params, shipmentType)), formatDetail(peak(params))); +}; + const ntsPackingFactor = (params) => { const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.NTSPackingFactor, params) || ''; const label = SERVICE_ITEM_CALCULATION_LABELS.NTSPackingFactor; @@ -480,6 +520,12 @@ const unpackPrice = (params, shipmentType) => { ); }; +const internationalUnpackPrice = (params, shipmentType) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.UnpackPriceInternational; + return calculation(value, label, formatDetail(referenceDate(params, shipmentType)), formatDetail(peak(params))); +}; + const additionalDayOriginSITPrice = (params, shipmentType) => { const value = getPriceRateOrFactor(params); const label = SERVICE_ITEM_CALCULATION_LABELS.AdditionalDaySITPrice; @@ -860,6 +906,50 @@ export default function makeCalculations(itemCode, totalAmount, params, mtoParam totalAmountRequested(totalAmount), ]; break; + case SERVICE_ITEM_CODES.ISLH: + result = [ + billableWeight(params), + internationalShippingAndLineHaulPrice(params, shipmentType), + priceEscalationFactor(params), + totalAmountRequested(totalAmount), + ]; + break; + // International packing + case SERVICE_ITEM_CODES.IHPK: + result = [ + billableWeight(params), + internationalPackPrice(params, shipmentType), + priceEscalationFactor(params), + totalAmountRequested(totalAmount), + ]; + break; + // International unpacking + case SERVICE_ITEM_CODES.IHUPK: + result = [ + billableWeight(params), + internationalUnpackPrice(params, shipmentType), + priceEscalationFactor(params), + totalAmountRequested(totalAmount), + ]; + break; + // Port of Debarkation Fuel surcharge + case SERVICE_ITEM_CODES.PODFSC: + result = [ + billableWeight(params), + mileageZipPODFSC(params), + mileageFactor(params, itemCode), + totalAmountRequested(totalAmount), + ]; + break; + // Port of Embarkation Fuel surcharge + case SERVICE_ITEM_CODES.POEFSC: + result = [ + billableWeight(params), + mileageZipPOEFSC(params), + mileageFactor(params, itemCode), + totalAmountRequested(totalAmount), + ]; + break; default: break; } diff --git a/src/components/Office/ServiceItemCalculations/helpers.test.js b/src/components/Office/ServiceItemCalculations/helpers.test.js index d1af4f1f958..8ca38112f65 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.test.js +++ b/src/components/Office/ServiceItemCalculations/helpers.test.js @@ -334,13 +334,33 @@ describe('DomesticDestinationSITDelivery', () => { testAB(result, expected); }); - // it('returns correct data for DomesticMobileHomeFactor', () => { - // const result = makeCalculations('?', 99999, testParams.DomesticMobileHomeFactor); - // expect(result).toEqual([]); - // }); - - // it('returns correct data for DomesticTowAwayBoatFactor', () => { - // const result = makeCalculations('?', 99999, testParams.DomesticTowAwayBoatFactor); - // expect(result).toEqual([]); - // }); + it('returns correct data for ISLH', () => { + const result = makeCalculations('ISLH', 99999, testParams.InternationalShippingAndLinehaul); + const expected = testData('ISLH'); + testAB(result, expected); + }); + + it('returns correct data for IHPK', () => { + const result = makeCalculations('IHPK', 99999, testParams.InternationalHHGPack); + const expected = testData('IHPK'); + testAB(result, expected); + }); + + it('returns correct data for IHUPK', () => { + const result = makeCalculations('IHUPK', 99999, testParams.InternationalHHGUnpack); + const expected = testData('IHUPK'); + testAB(result, expected); + }); + + it('returns correct data for POEFSC', () => { + const result = makeCalculations('POEFSC', 99998, testParams.PortOfEmbarkation); + const expected = testData('POEFSC'); + testAB(result, expected); + }); + + it('returns correct data for PODFSC', () => { + const result = makeCalculations('PODFSC', 99998, testParams.PortOfDebarkation); + const expected = testData('PODFSC'); + testAB(result, expected); + }); }); diff --git a/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js b/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js index 6722efed907..e234dc167c9 100644 --- a/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js +++ b/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js @@ -475,6 +475,24 @@ const StandaloneCrate = { type: 'BOOLEAN', value: 'FALSE', }; +const PerUnitCents = { + eTag: 'MjAyMS0wMy0xOFQwMTozMTo1MS4zNTI1MDZb', + id: 'b26fcc8f-2c06-4b00-8b51-4715a2eb0f34', + key: 'ZipDestAddress', + origin: 'SYSTEM', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'INTEGER', + value: '1000', +}; +const PortZip = { + eTag: 'MjAyMS0wMy0xOFQwMTozMTo1MS4zNTI1MDZc', + id: 'b26fcc8f-2c06-4b00-8b51-4715a2eb0f35', + key: 'ZipDestAddress', + origin: 'SYSTEM', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'STRING', + value: '99505', +}; const testParams = { DomesticLongHaul: [ ContractCode, @@ -843,6 +861,61 @@ const testParams = { PSIPriceDomDest, PSIPriceDomDestPrice, ], + InternationalHHGPack: [ + ContractYearName, + EscalationCompounded, + PriceRateOrFactor, + ReferenceDate, + WeightOriginal, + WeightBilled, + WeightEstimated, + ZipPickupAddress, + PerUnitCents, + ], + InternationalHHGUnpack: [ + ContractYearName, + EscalationCompounded, + PriceRateOrFactor, + ReferenceDate, + WeightOriginal, + WeightBilled, + WeightEstimated, + ZipPickupAddress, + PerUnitCents, + ], + InternationalShippingAndLinehaul: [ + ContractYearName, + EscalationCompounded, + PriceRateOrFactor, + ReferenceDate, + WeightOriginal, + WeightBilled, + WeightEstimated, + ZipPickupAddress, + PerUnitCents, + ], + PortOfDebarkation: [ + ActualPickupDate, + DistanceZip, + EIAFuelPrice, + FSCPriceDifferenceInCents, + FSCWeightBasedDistanceMultiplier, + WeightBilled, + WeightEstimated, + ZipDestAddress, + PortZip, + ], + PortOfEmbarkation: [ + ActualPickupDate, + DistanceZip, + EIAFuelPrice, + FSCPriceDifferenceInCents, + FSCWeightBasedDistanceMultiplier, + WeightBilled, + WeightEstimated, + ZipPickupAddress, + PortZip, + ], additionalCratingDataDCRT: { reServiceCode: 'DCRT', description: 'Grand piano', diff --git a/src/constants/serviceItems.js b/src/constants/serviceItems.js index cb0b9bcb902..31e47819527 100644 --- a/src/constants/serviceItems.js +++ b/src/constants/serviceItems.js @@ -24,6 +24,8 @@ const SERVICE_ITEM_PARAM_KEYS = { NTSPackingFactor: 'NTSPackingFactor', NumberDaysSIT: 'NumberDaysSIT', OriginPrice: 'OriginPrice', + PerUnitCents: 'PerUnitCents', + PortZip: 'PortZip', PriceRateOrFactor: 'PriceRateOrFactor', ReferenceDate: 'ReferenceDate', RequestedDeliveryDate: 'RequestedDeliveryDate', @@ -64,6 +66,7 @@ const SERVICE_ITEM_CALCULATION_LABELS = { // Domestic non-peak or Domestic peak [SERVICE_ITEM_PARAM_KEYS.IsPeak]: 'Domestic', [SERVICE_ITEM_PARAM_KEYS.OriginPrice]: 'Origin price', + [SERVICE_ITEM_PARAM_KEYS.PortZip]: 'Port ZIP', [SERVICE_ITEM_PARAM_KEYS.ReferenceDate]: 'Requested pickup', [SERVICE_ITEM_PARAM_KEYS.RequestedPickupDate]: 'Requested pickup', [SERVICE_ITEM_PARAM_KEYS.ServiceAreaOrigin]: 'Origin service area', @@ -98,12 +101,14 @@ const SERVICE_ITEM_CALCULATION_LABELS = { Dimensions: 'Dimensions', Domestic: 'Domestic', FuelSurchargePrice: 'Mileage factor', + InternationalShippingAndLinehaul: 'ISLH price', Mileage: 'Mileage', MileageIntoSIT: 'Mileage into SIT', MileageOutOfSIT: 'Mileage out of SIT', NTSPackingFactor: 'NTS packing factor', NTSReleaseReferenceDate: 'Actual pickup', PackPrice: 'Pack price', + PackPriceInternational: 'International Pack price', PickupDate: 'Pickup date', PickupSITPrice: 'SIT pickup price', PriceEscalationFactor: 'Price escalation factor', @@ -112,6 +117,7 @@ const SERVICE_ITEM_CALCULATION_LABELS = { SITDeliveryPrice: 'SIT delivery price', FuelRateAdjustment: 'Fuel rate adjustment', UnpackPrice: 'Unpack price', + UnpackPriceInternational: 'International Unpack price', UncratingDate: 'Uncrating date', UncratingPrice: 'Uncrating price (per cu ft)', SITFuelSurchargePrice: 'SIT mileage factor', @@ -221,6 +227,11 @@ const allowedServiceItemCalculations = [ SERVICE_ITEM_CODES.DUCRT, SERVICE_ITEM_CODES.DOSFSC, SERVICE_ITEM_CODES.DDSFSC, + SERVICE_ITEM_CODES.IHPK, + SERVICE_ITEM_CODES.IHUPK, + SERVICE_ITEM_CODES.ISLH, + SERVICE_ITEM_CODES.POEFSC, + SERVICE_ITEM_CODES.PODFSC, ]; export default SERVICE_ITEM_STATUSES; diff --git a/src/content/serviceItems.js b/src/content/serviceItems.js index cf9e614427c..e64835eae41 100644 --- a/src/content/serviceItems.js +++ b/src/content/serviceItems.js @@ -30,8 +30,8 @@ export const serviceItemCodes = { IDDSIT: 'International destination SIT delivery', IDFSIT: 'International destination 1st day SIT', IDSHUT: 'International destination shuttle service', - IHPK: 'International HHG pack', - IHUPK: 'International HHG unpack', + IHPK: 'International HHG Pack', + IHUPK: 'International HHG Unpack', INPK: 'International NTS packing', IOASIT: "International origin add'l day SIT", IOCLH: 'International O->C shipping & LH',