@trevordsouzabrite/test-package 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.claude/agents/playwright-test-generator.md +85 -0
  2. package/.claude/agents/playwright-test-healer.md +45 -0
  3. package/.claude/agents/playwright-test-planner.md +52 -0
  4. package/.claude/prompts/playwright-test-coverage.md +31 -0
  5. package/.claude/prompts/playwright-test-generate.md +12 -0
  6. package/.claude/prompts/playwright-test-heal.md +6 -0
  7. package/.claude/prompts/playwright-test-plan.md +12 -0
  8. package/.claude/settings.local.json +31 -0
  9. package/.github/agents/playwright-test-generator.agent.md +113 -0
  10. package/.github/agents/playwright-test-healer.agent.md +70 -0
  11. package/.github/agents/playwright-test-planner.agent.md +82 -0
  12. package/.github/prompts/playwright-test-coverage.prompt.md +31 -0
  13. package/.github/prompts/playwright-test-generate.prompt.md +12 -0
  14. package/.github/prompts/playwright-test-heal.prompt.md +6 -0
  15. package/.github/prompts/playwright-test-plan.prompt.md +9 -0
  16. package/.github/workflows/copilot-setup-steps.yml +34 -0
  17. package/.github/workflows/playwright-healer-agent.yml +140 -0
  18. package/.github/workflows/playwright.yml +40 -0
  19. package/.mcp.json +13 -0
  20. package/.vscode/extensions.json +6 -0
  21. package/.vscode/mcp.json +13 -0
  22. package/.vscode/settings.example.json +15 -0
  23. package/bitbucket-pipelines.yml +86 -0
  24. package/lib/WebActions.ts +107 -0
  25. package/package.json +33 -0
  26. package/pageRepository/ApplicantPage.ts +1171 -0
  27. package/pageRepository/CreateApplicationPage.ts +1736 -0
  28. package/playwright/.auth/user.json +0 -0
  29. package/specs/Applicant Create Application Page Test Plan.md +440 -0
  30. package/specs/Applicant Dashboard Page Test Plan.md +74 -0
  31. package/specs/Applicant Forgot Password Page Test Plan.md +112 -0
  32. package/specs/Applicant Help Page Test Plan.md +369 -0
  33. package/specs/Applicant Landing Page Test Plan.md +42 -0
  34. package/specs/Applicant Login Page Test Plan.md +116 -0
  35. package/specs/Applicant My Applications Page Test Plan.md +558 -0
  36. package/specs/Applicant My Medical Coverage Page Test Plan.md +689 -0
  37. package/specs/Applicant Privacy Policy Page Test Plan.md +196 -0
  38. package/specs/Applicant Resources Page Test Plan.md +107 -0
  39. package/specs/Applicant Self Register Page Test Plan.md +190 -0
  40. package/specs/README.md +3 -0
  41. package/test-data/Sample.png +0 -0
  42. package/test-data/createApplication/formData.json +42 -0
  43. package/test-data/createApplication/textMessages.json +52 -0
  44. package/test-data/forgotPassword/email.json +5 -0
  45. package/test-data/forgotPassword/textMessages.json +5 -0
  46. package/test-data/help/textContent.json +48 -0
  47. package/test-data/login/invalidUsernamePassword.json +4 -0
  48. package/test-data/login/textMessages.json +5 -0
  49. package/test-data/privacyPolicy/textContent.json +25 -0
  50. package/test-data/selfRegister/mailingAddressStates.json +21 -0
  51. package/test-data/selfRegister/registrationFieldData.json +13 -0
  52. package/test-data/selfRegister/suffix.json +3 -0
  53. package/test-data/selfRegister/textMessages.json +13 -0
  54. package/test-data/test-data.zip +0 -0
  55. package/tests/ApplicantCreateApplicationPageTest.spec.ts +1452 -0
  56. package/tests/ApplicantDashboardPageTest.spec.ts +74 -0
  57. package/tests/ApplicantForgotPasswordPageTest.spec.ts +88 -0
  58. package/tests/ApplicantHelpPageTest.spec.ts +468 -0
  59. package/tests/ApplicantLandingPageTest.spec.ts +33 -0
  60. package/tests/ApplicantLoginPageTest.spec.ts +117 -0
  61. package/tests/ApplicantMyApplicationsPageTest.spec.ts +516 -0
  62. package/tests/ApplicantMyMedicalCoveragePageTest.spec.ts +470 -0
  63. package/tests/ApplicantPrivacyPolicyPageTest.spec.ts +188 -0
  64. package/tests/ApplicantResourcesPageTest.spec.ts +117 -0
  65. package/tests/ApplicantSelfRegisterPageTest.spec.ts +254 -0
  66. package/tests/auth.setup.ts +42 -0
  67. package/tests/authState.ts +15 -0
  68. package/tests/example.spec.ts +18 -0
  69. package/tests/seed.spec.ts +7 -0
@@ -0,0 +1,470 @@
1
+ // spec: specs/Applicant My Medical Coverage Page Test Plan.md
2
+ // seed: seed.spec.ts
3
+
4
+ import { test, expect } from '@playwright/test';
5
+ import AxeBuilder from '@axe-core/playwright';
6
+ import { ApplicantPage } from '@pages/ApplicantPage';
7
+ import { WebActions } from '@lib/WebActions';
8
+ import { expectApplicantAuthenticatedHeaderFooterVisible } from '@lib/ApplicantPortalAssertions';
9
+ import { testConfig } from '../testConfig';
10
+
11
+ const MY_MEDICAL_COVERAGE_PATH = '/polkphpapplicant/s/enrollments?language=en_US';
12
+ const BILLING_INQUIRIES_PATH = '/polkphpapplicant/s/billing-inquiries?language=en_US';
13
+
14
+ function containsReadableCoverageDate(text: string): boolean {
15
+ return /\b(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},\s+\d{4}\b/.test(text);
16
+ }
17
+
18
+ test.describe('Applicant My Medical Coverage Page Tests', () => {
19
+ test.describe('Authenticated My Medical Coverage', () => {
20
+ test.skip(process.env.AUTH !== 'true', 'Authenticated My Medical Coverage tests require AUTH=true and playwright/.auth/user.json.');
21
+ test.describe.configure({ mode: 'serial', timeout: 120_000 });
22
+
23
+ let applicantPage: ApplicantPage;
24
+ let webActions: WebActions;
25
+
26
+ test.beforeEach(async ({ page }) => {
27
+ applicantPage = new ApplicantPage(page);
28
+ webActions = new WebActions(page, page.context());
29
+ await applicantPage.gotoMyMedicalCoveragePage();
30
+ await webActions.waitForElementAttached(applicantPage.Logout_Btn);
31
+ });
32
+
33
+ test('TC-01 - Authenticated My Medical Coverage Page Loads @auth ', async ({ page }) => {
34
+ // 1. Start from an authenticated applicant session.
35
+ await applicantPage.waitForMyMedicalCoveragePageReady();
36
+ // 2. Navigate to the applicant dashboard. 3. Click My Medical Coverage. (done in beforeEach)
37
+ // 4. Verify the URL contains /enrollments.
38
+ await expect.soft(page).toHaveURL(/\/polkphpapplicant\/s\/enrollments/i);
39
+ // 5. Verify browser title is Enrollments.
40
+ await expect.soft(page).toHaveTitle(/Enrollments/i);
41
+ });
42
+
43
+ test('TC-02 - Authenticated Header And Footer Are Visible @auth', async () => {
44
+ // 1. Open My Medical Coverage.
45
+ await applicantPage.waitForMyMedicalCoveragePageReady();
46
+ // 2-4. Verify authenticated header, Logout, and footer content.
47
+ await expectApplicantAuthenticatedHeaderFooterVisible(applicantPage);
48
+ });
49
+
50
+ test('TC-03 - Enrollment Summary Table Displays For Applicant With Coverage @auth ', async () => {
51
+ test.skip(!(await applicantPage.hasMedicalCoverageRecords()), 'Requires an applicant account with at least one enrollment record.');
52
+ // 1. Open My Medical Coverage.
53
+ await applicantPage.waitForMyMedicalCoveragePageReady();
54
+ // 2. Verify the enrollment summary area is visible.
55
+ await expect.soft(applicantPage.MyMedicalCoverageFirstEnrollmentRow_Block).toBeVisible();
56
+ // 3. Verify columns are shown.
57
+ await expect.soft(applicantPage.MyMedicalCoverageBeneficiaryHeader_Text).toBeVisible();
58
+ await expect.soft(applicantPage.MyMedicalCoverageProgramHeader_Text).toBeVisible();
59
+ await expect.soft(applicantPage.MyMedicalCoverageStatusHeader_Text).toBeVisible();
60
+ await expect.soft(applicantPage.MyMedicalCoverageCoverageBeginDateHeader_Text).toBeVisible();
61
+ await expect.soft(applicantPage.MyMedicalCoverageCoverageEndDateHeader_Text).toBeVisible();
62
+ await expect.soft(applicantPage.MyMedicalCoverageActionsHeader_Text).toBeVisible();
63
+ // 4. Verify each visible enrollment row has key values.
64
+ const rowText = await applicantPage.getFirstEnrollmentRowText();
65
+ const pageText = await applicantPage.getMyMedicalCoveragePageText();
66
+ expect.soft(rowText).toMatch(/PHP|Active|Inactive|Expired|Terminated/i);
67
+ expect.soft(containsReadableCoverageDate(pageText)).toBe(true);
68
+ });
69
+
70
+ test('TC-04 - Enrollment Row Date And Status Formatting @auth', async () => {
71
+ test.skip(!(await applicantPage.hasMedicalCoverageRecords()), 'Requires an applicant account with at least one enrollment record.');
72
+ // 1. Open My Medical Coverage for an applicant with coverage.
73
+ await applicantPage.waitForMyMedicalCoveragePageReady();
74
+ // 2-4. Read status and coverage date values, then verify readable formatting.
75
+ const rowText = await applicantPage.getFirstEnrollmentRowText();
76
+ const pageText = await applicantPage.getMyMedicalCoveragePageText();
77
+ expect.soft(rowText).toMatch(/Active|Inactive|Expired|Terminated|Pending/i);
78
+ expect.soft(containsReadableCoverageDate(pageText)).toBe(true);
79
+ });
80
+
81
+ test('TC-05 - No Enrollment Empty State @auth', async () => {
82
+ test.skip(await applicantPage.hasMedicalCoverageRecords(), 'Requires an applicant account with no enrollment records.');
83
+ // 1. Open My Medical Coverage.
84
+ await applicantPage.waitForMyMedicalCoveragePageReady();
85
+ // 2. Wait for enrollment content to finish loading.
86
+ // 3. Verify the page shows DID NOT FIND ANY ENROLLMENT RECORDS.
87
+ await expect.soft(applicantPage.NoEnrollments_Text).toBeVisible();
88
+ // 4. Verify header, footer, and main navigation remain available.
89
+ await expectApplicantAuthenticatedHeaderFooterVisible(applicantPage);
90
+ });
91
+
92
+ test('TC-06 - Change Provider Modal Opens @auth ', async () => {
93
+ test.skip(!(await applicantPage.hasMedicalCoverageRecords()), 'Requires an enrollment with the Change Provider action.');
94
+ // 1. Open My Medical Coverage.
95
+ await applicantPage.waitForMyMedicalCoveragePageReady();
96
+ // 2. Click Change Provider.
97
+ await applicantPage.clickChangeProviderButton();
98
+ // 3. Verify the modal opens with title Change Provider.
99
+ await expect.soft(applicantPage.ChangeProviderDialog_Block).toBeVisible();
100
+ await expect.soft(applicantPage.ChangeProviderHeading_Text).toBeVisible();
101
+ // 4. Verify instruction text.
102
+ await expect.soft(applicantPage.ChangeProviderInstructions_Text).toBeVisible();
103
+ // 5. Verify required fields are visible.
104
+ await expect.soft(applicantPage.ChangeProviderSearch_Input).toBeVisible();
105
+ await expect.soft(applicantPage.ChangeProviderReason_dd).toBeVisible();
106
+ // 6. Verify Close and Submit controls are visible.
107
+ await expect.soft(applicantPage.ChangeProviderClose_Btn).toBeVisible();
108
+ await expect.soft(applicantPage.ChangeProviderSubmit_Btn).toBeVisible();
109
+ });
110
+
111
+ test('TC-07 - Change Provider Empty Submit Validation @auth @validation', async () => {
112
+ test.skip(!(await applicantPage.hasMedicalCoverageRecords()), 'Requires an enrollment with the Change Provider action.');
113
+ // 1. Open the Change Provider modal.
114
+ await applicantPage.clickChangeProviderButton();
115
+ // 2. Leave provider search blank.
116
+ // 3. Leave reason dropdown unselected.
117
+ // 4. Click Submit.
118
+ await applicantPage.submitChangeProviderRequest();
119
+ // Verify required field messages and blocked submission.
120
+ await expect.soft(applicantPage.ChangeProviderRequiredFieldErrors_Text.first()).toBeVisible();
121
+ await expect.soft(applicantPage.ChangeProviderRequiredFieldErrors_Text).toHaveCount(2);
122
+ await expect.soft(applicantPage.ChangeProviderErrorNotification_Text).toBeVisible();
123
+ await expect.soft(applicantPage.ChangeProviderDialog_Block).toBeVisible();
124
+ });
125
+
126
+ test('TC-08 - Change Provider Close Does Not Submit @auth', async () => {
127
+ test.skip(!(await applicantPage.hasMedicalCoverageRecords()), 'Requires an enrollment with the Change Provider action.');
128
+ // 1. Open the Change Provider modal.
129
+ await applicantPage.clickChangeProviderButton();
130
+ const beforeCloseText = await applicantPage.getFirstEnrollmentRowText();
131
+ // 2. Click Close without entering data.
132
+ await applicantPage.closeChangeProviderDialog();
133
+ // 3. Reopen My Medical Coverage or inspect the enrollment row.
134
+ await expect.soft(applicantPage.ChangeProviderDialog_Block).not.toBeVisible();
135
+ expect.soft(await applicantPage.getFirstEnrollmentRowText()).toContain(beforeCloseText.split(' ')[0]);
136
+ });
137
+
138
+ test.fixme('TC-09 - Change Provider Provider Search Behavior @auth', async () => {
139
+ // Requires stable provider test data for lookup selection.
140
+ });
141
+
142
+ test.fixme('TC-10 - Change Provider No-Match Search @auth @edge', async () => {
143
+ // Requires product-approved expectations for provider lookup no-result text.
144
+ });
145
+
146
+ test.fixme('TC-11 - Change Provider Submit With Missing Reason @auth @validation', async () => {
147
+ // Requires stable provider test data to select a provider without submitting a real request.
148
+ });
149
+
150
+ test.fixme('TC-12 - Change Provider Submit With Missing Provider @auth @validation', async () => {
151
+ // Requires stable reason dropdown option data.
152
+ });
153
+
154
+ test.fixme('TC-13 - Change Provider Successful Submission @auth', async () => {
155
+ // Data-changing scenario; requires a controlled account where creating a provider change request is safe.
156
+ });
157
+
158
+ test('TC-14 - My Request Status Displays Current Provider Change Request @auth', async () => {
159
+ // 1. Open My Medical Coverage.
160
+ await applicantPage.waitForMyMedicalCoveragePageReady();
161
+ // 2. Inspect the My Request Status section.
162
+ await expect.soft(applicantPage.MyMedicalCoverageRequestStatusHeading_Text).toBeVisible();
163
+ // 3. Verify columns or labels such as Status, Effective Date, and Reference.
164
+ await expect.soft(applicantPage.MyMedicalCoverageRequestStatusStatus_Text).toBeVisible();
165
+ await expect.soft(applicantPage.MyMedicalCoverageRequestStatusEffectiveDate_Text).toBeVisible();
166
+ await expect.soft(applicantPage.MyMedicalCoverageRequestStatusReference_Text).toBeVisible();
167
+ // 4. Verify the current provider reference is displayed when present.
168
+ await expect.soft(applicantPage.MyMedicalCoverageRequestStatus_Block).toBeVisible();
169
+ });
170
+
171
+ test('TC-15 - Enrollment Card Generates PDF @auth ', async ({ page }) => {
172
+ test.skip(!(await applicantPage.hasMedicalCoverageRecords()), 'Requires an enrollment with the Enrollment card action.');
173
+ // 1. Open My Medical Coverage.
174
+ await applicantPage.waitForMyMedicalCoveragePageReady();
175
+ // 2. Click Enrollment card for an enrollment row.
176
+ const downloadPromise = page.waitForEvent('download', { timeout: 30_000 });
177
+ await applicantPage.clickEnrollmentCardButton();
178
+ const download = await downloadPromise;
179
+ // 3. Wait for navigation or download.
180
+ // 4. Verify the browser navigates to /polkphpapplicant/s/enrollmentcard.
181
+ await expect.soft(page).toHaveURL(/\/polkphpapplicant\/s\/enrollmentcard/i);
182
+ // 5. Verify a PDF download is initiated.
183
+ expect.soft(download.suggestedFilename()).toBe('EnrollmentCard.pdf');
184
+ });
185
+
186
+ test.fixme('TC-16 - Enrollment Card Uses Selected Enrollment Record @auth', async () => {
187
+ // Requires an applicant account with multiple enrollment records.
188
+ });
189
+
190
+ test('TC-17 - Update My Information Action @auth', async () => {
191
+ // 1. Open My Medical Coverage.
192
+ await applicantPage.waitForMyMedicalCoveragePageReady();
193
+ // 2. Click Update My Information.
194
+ await applicantPage.clickUpdateMyInformationButton();
195
+ // 3. Wait for navigation, modal, or toast.
196
+ await expect.soft(applicantPage.UpdateMyInformationErrorToast_Text).toBeVisible();
197
+ });
198
+
199
+ test('TC-18 - Update My Information Error Is Dismissible @auth @validation', async () => {
200
+ // 1. Click Update My Information for an account without a matching application.
201
+ await applicantPage.clickUpdateMyInformationButton();
202
+ // 2. Verify the Error No application found. toast appears.
203
+ await expect.soft(applicantPage.UpdateMyInformationErrorToast_Text).toBeVisible();
204
+ // 3. Click the toast Close control.
205
+ await applicantPage.dismissToastIfVisible();
206
+ await expect.soft(applicantPage.MyMedicalCoverageBody_Block).toBeVisible();
207
+ });
208
+
209
+ test('TC-19 - Billing Inquiries Page Opens @auth ', async ({ page }) => {
210
+ // 1. Open My Medical Coverage.
211
+ await applicantPage.waitForMyMedicalCoveragePageReady();
212
+ // 2. Click Billing Inquiries.
213
+ await applicantPage.clickBillingInquiriesButton();
214
+ // 3. Wait for navigation.
215
+ // 4. Verify URL contains /billing-inquiries.
216
+ await expect.soft(page).toHaveURL(/\/polkphpapplicant\/s\/billing-inquiries/i);
217
+ // 5. Verify browser title is Billing_Inquiries.
218
+ await expect.soft(page).toHaveTitle(/Billing_Inquiries/i);
219
+ // 6. Verify heading text and explanatory instructions are visible.
220
+ await expect.soft(applicantPage.BillingInquiryHeading_Text).toBeVisible();
221
+ await expect.soft(applicantPage.BillingInquiryInstructions_Text).toBeVisible();
222
+ });
223
+
224
+ test('TC-20 - Billing Inquiry Form Required Fields Are Visible @auth', async () => {
225
+ // 1. Open Billing Inquiries from My Medical Coverage.
226
+ await applicantPage.clickBillingInquiriesButton();
227
+ // 2. Verify required fields.
228
+ await expect.soft(applicantPage.BillingInquiryCoverageReason_Text).toBeVisible();
229
+ await expect.soft(applicantPage.BillingInquiryDateOfService_Input).toBeVisible();
230
+ await expect.soft(applicantPage.BillingInquiryAmountDue_Input).toBeVisible();
231
+ await expect.soft(applicantPage.BillingInquiryApplicationMember_dd).toBeVisible();
232
+ await expect.soft(applicantPage.BillingInquiryAppealReason_dd).toBeVisible();
233
+ await expect.soft(applicantPage.BillingInquiryProviderFacilityName_Input).toBeVisible();
234
+ await expect.soft(applicantPage.BillingInquiryDocumentType_dd).toBeVisible();
235
+ // 3. Verify optional/conditional fields.
236
+ await expect.soft(applicantPage.BillingInquiryOtherPayer_dd).toBeVisible();
237
+ await expect.soft(applicantPage.BillingInquiryClaimNumber_Input).toBeVisible();
238
+ await expect.soft(applicantPage.BillingInquiryAmountPaidByOtherProvider_Input).toBeVisible();
239
+ // 4. Verify file upload area is visible.
240
+ await expect.soft(applicantPage.BillingInquiryUploadFiles_Btn).toBeVisible();
241
+ await expect.soft(applicantPage.BillingInquiryUploadDropzone_Text).toBeVisible();
242
+ });
243
+
244
+ test('TC-21 - Billing Inquiry Empty Submit Requires Upload @auth @validation', async () => {
245
+ // 1. Open Billing Inquiries.
246
+ await applicantPage.clickBillingInquiriesButton();
247
+ // 2. Do not upload a file.
248
+ // 3. Click Submit.
249
+ await applicantPage.submitBillingInquiry();
250
+ // Verify upload-required toast.
251
+ await expect.soft(applicantPage.BillingInquiryUploadRequiredToast_Text).toBeVisible();
252
+ });
253
+
254
+ test.fixme('TC-22 - Billing Inquiry Missing Required Field Validation @auth @validation', async () => {
255
+ // Requires a stable upload fixture and field-level validation expectations after upload.
256
+ });
257
+
258
+ test.fixme('TC-23 - Billing Inquiry File Upload Accepts Supported Documents @auth', async () => {
259
+ // Requires stable upload control selectors and cleanup behavior.
260
+ });
261
+
262
+ test.fixme('TC-24 - Billing Inquiry Rejects Unsupported Or Oversized Files @auth @edge', async () => {
263
+ // Requires product-defined file type and size limits.
264
+ });
265
+
266
+ test.fixme('TC-25 - Billing Inquiry Amount Validation @auth @validation', async () => {
267
+ // Requires field-level validation expectations for amount formats.
268
+ });
269
+
270
+ test.fixme('TC-26 - Billing Inquiry Date Validation @auth @validation', async () => {
271
+ // Requires product-defined rules for invalid and future service dates.
272
+ });
273
+
274
+ test('TC-27 - Billing Inquiry Cancel Returns Safely @auth', async ({ page }) => {
275
+ // 1. Open Billing Inquiries.
276
+ await applicantPage.clickBillingInquiriesButton();
277
+ await expect.soft(page).toHaveURL(/\/polkphpapplicant\/s\/billing-inquiries/i);
278
+ // 2. Enter partial form data. (skipped to avoid dirty-form confirmation in shared data)
279
+ // 3. Click Cancel.
280
+ await applicantPage.cancelBillingInquiry();
281
+ // 4. Confirm any unsaved-change prompt if shown.
282
+ await expect.soft(applicantPage.MyMedicalCoverageBody_Block).toBeVisible();
283
+ });
284
+
285
+ test.fixme('TC-28 - Billing Inquiry Successful Submission @auth', async () => {
286
+ // Data-changing scenario; requires controlled test data where creating a billing inquiry is allowed.
287
+ });
288
+
289
+ test('TC-31 - Logout Protects My Medical Coverage @auth', async ({ page }) => {
290
+ // 1. Open My Medical Coverage with an authenticated session.
291
+ await applicantPage.waitForMyMedicalCoveragePageReady();
292
+ // 2. Click Logout.
293
+ await applicantPage.clickLogoutButton();
294
+ await expect.soft(page).toHaveURL(/\/polkphpapplicant\/s\/login|\/secur\/logout/i);
295
+ // 3. Attempt to navigate back to /polkphpapplicant/s/enrollments?language=en_US.
296
+ await applicantPage.gotoURL(MY_MEDICAL_COVERAGE_PATH, { handleConsentUpdate: false });
297
+ await expect.soft(page).toHaveURL(/\/polkphpapplicant\/s\/login/i);
298
+ });
299
+
300
+ test.fixme('TC-32 - Unauthorized Enrollment Record Access @auth @security', async () => {
301
+ // Requires a known invalid or cross-applicant enrollment record ID supplied by secure test data.
302
+ });
303
+
304
+ test.fixme('TC-33 - Multiple Enrollment Records @auth @edge', async () => {
305
+ // Requires an applicant account with multiple active or historical enrollments.
306
+ });
307
+
308
+ test.fixme('TC-34 - Expired Or Inactive Coverage Display @auth @edge', async () => {
309
+ // Requires an applicant account with inactive, expired, terminated, or future-dated coverage.
310
+ });
311
+
312
+ test('TC-35 - Keyboard Navigation @auth @accessibility', async ({ page }) => {
313
+ // 1. Open My Medical Coverage.
314
+ await applicantPage.waitForMyMedicalCoveragePageReady();
315
+ // 2. Use Tab and Shift+Tab through page controls.
316
+ await page.keyboard.press('Tab');
317
+ await page.keyboard.press('Tab');
318
+ await page.keyboard.press('Shift+Tab');
319
+ // 3. Activate Change Provider using keyboard controls when available.
320
+ if (await applicantPage.hasMedicalCoverageRecords()) {
321
+ await applicantPage.MyMedicalCoverageChangeProvider_Btn.focus();
322
+ await page.keyboard.press('Enter');
323
+ await expect.soft(applicantPage.ChangeProviderDialog_Block).toBeVisible();
324
+ // 4. Dismiss modals or toasts with keyboard controls.
325
+ await page.keyboard.press('Escape');
326
+ }
327
+ await expect.soft(applicantPage.MyMedicalCoverageBody_Block).toBeVisible();
328
+ });
329
+
330
+ test('TC-36 - Accessible Names And Semantics @auth @accessibility', async () => {
331
+ // 1. Open My Medical Coverage.
332
+ await applicantPage.waitForMyMedicalCoveragePageReady();
333
+ // 2. Verify row action controls have clear accessible names.
334
+ if (await applicantPage.hasMedicalCoverageRecords()) {
335
+ await expect.soft(applicantPage.MyMedicalCoverageChangeProvider_Btn).toBeVisible();
336
+ await expect.soft(applicantPage.MyMedicalCoverageEnrollmentCard_Btn).toBeVisible();
337
+ }
338
+ // 3. Verify the enrollment table-like region exposes column labels.
339
+ await expect.soft(applicantPage.MyMedicalCoverageBeneficiaryHeader_Text).toBeVisible();
340
+ await expect.soft(applicantPage.MyMedicalCoverageCoverageBeginDateHeader_Text).toBeVisible();
341
+ // 4. Open Change Provider and verify modal semantics when available.
342
+ if (await applicantPage.hasMedicalCoverageRecords()) {
343
+ await applicantPage.clickChangeProviderButton();
344
+ await expect.soft(applicantPage.ChangeProviderDialog_Block).toBeVisible();
345
+ await expect.soft(applicantPage.ChangeProviderSearch_Input).toBeVisible();
346
+ await applicantPage.closeChangeProviderDialog();
347
+ }
348
+ // 5. Open Billing Inquiries and verify controls are accessible.
349
+ await applicantPage.clickBillingInquiriesButton();
350
+ await expect.soft(applicantPage.BillingInquirySubmit_Btn).toBeVisible();
351
+ await expect.soft(applicantPage.BillingInquiryCancel_Btn).toBeVisible();
352
+ });
353
+
354
+ test('TC-37 - Axe Accessibility Baseline @auth @accessibility', async ({ page }) => {
355
+ // 1. Open My Medical Coverage.
356
+ await applicantPage.waitForMyMedicalCoveragePageReady();
357
+ // 2. Run Axe with WCAG 2.x A/AA rules.
358
+ let accessibilityScanResults = await new AxeBuilder({ page })
359
+ .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
360
+ .analyze();
361
+ expect.soft(accessibilityScanResults.violations.filter((violation) => ['critical', 'serious'].includes(violation.impact ?? ''))).toEqual([]);
362
+ // 3. Repeat after opening the Change Provider modal.
363
+ if (await applicantPage.hasMedicalCoverageRecords()) {
364
+ await applicantPage.clickChangeProviderButton();
365
+ accessibilityScanResults = await new AxeBuilder({ page })
366
+ .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
367
+ .analyze();
368
+ expect.soft(accessibilityScanResults.violations.filter((violation) => ['critical', 'serious'].includes(violation.impact ?? ''))).toEqual([]);
369
+ }
370
+ // 4. Repeat on Billing Inquiries.
371
+ await applicantPage.gotoURL(BILLING_INQUIRIES_PATH);
372
+ accessibilityScanResults = await new AxeBuilder({ page })
373
+ .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
374
+ .analyze();
375
+ expect.soft(accessibilityScanResults.violations.filter((violation) => ['critical', 'serious'].includes(violation.impact ?? ''))).toEqual([]);
376
+ });
377
+
378
+ test('TC-38 - Responsive Layout @auth', async ({ page }) => {
379
+ for (const viewport of [
380
+ { width: 1500, height: 730 },
381
+ { width: 768, height: 1024 },
382
+ { width: 412, height: 732 },
383
+ ]) {
384
+ // 1-3. Open My Medical Coverage at desktop, tablet, and mobile viewport sizes.
385
+ await page.setViewportSize(viewport);
386
+ await applicantPage.gotoMyMedicalCoveragePage();
387
+ // 4. Verify enrollment page, actions, header, and footer remain usable.
388
+ await expect.soft(applicantPage.Logout_Btn).toBeVisible();
389
+ await expect.soft(applicantPage.MyMedicalCoverageBody_Block).toBeVisible();
390
+ if (await applicantPage.hasMedicalCoverageRecords()) {
391
+ await expect.soft(applicantPage.MyMedicalCoverageChangeProvider_Btn).toBeVisible();
392
+ await expect.soft(applicantPage.MyMedicalCoverageEnrollmentCard_Btn).toBeVisible();
393
+ } else {
394
+ await expect.soft(applicantPage.NoEnrollments_Text).toBeVisible();
395
+ }
396
+ }
397
+ });
398
+
399
+ test('TC-39 - Browser Console And Network Health @auth', async ({ page }) => {
400
+ const consoleErrors: string[] = [];
401
+ const failedRequests: string[] = [];
402
+
403
+ page.on('console', (message) => {
404
+ if (message.type() === 'error') {
405
+ consoleErrors.push(message.text());
406
+ }
407
+ });
408
+ page.on('requestfailed', (request) => {
409
+ if (['document', 'xhr', 'fetch'].includes(request.resourceType())) {
410
+ failedRequests.push(`${request.resourceType()} ${request.url()} ${request.failure()?.errorText ?? ''}`);
411
+ }
412
+ });
413
+
414
+ // 1. Open My Medical Coverage.
415
+ await applicantPage.gotoMyMedicalCoveragePage();
416
+ // 2-3. Monitor initial load and action navigation.
417
+ if (await applicantPage.hasMedicalCoverageRecords()) {
418
+ await applicantPage.clickChangeProviderButton();
419
+ await applicantPage.closeChangeProviderDialog();
420
+ }
421
+ await applicantPage.clickBillingInquiriesButton();
422
+ // 4. Monitor console messages and failed network requests.
423
+ expect.soft(failedRequests).toEqual([]);
424
+ expect.soft(consoleErrors.filter((error) => !/ResizeObserver|Aura|Lightning|CSS Error/i.test(error))).toEqual([]);
425
+ });
426
+
427
+ test.fixme('TC-40 - Data Privacy In Enrollment And PDF Output @auth @security', async () => {
428
+ // Requires PDF parsing and secure test data expectations for the authenticated applicant.
429
+ });
430
+ });
431
+
432
+ test.describe('Unauthenticated My Medical Coverage Access', () => {
433
+ test.use({ storageState: { cookies: [], origins: [] } });
434
+
435
+ let applicantPage: ApplicantPage;
436
+
437
+ test.beforeEach(async ({ page }) => {
438
+ applicantPage = new ApplicantPage(page);
439
+ });
440
+
441
+ test('TC-29 - Direct Unauthenticated Access Redirects To Login @no-auth ', async ({ page }) => {
442
+ // 1. Create a new browser context without storage state.
443
+ await page.context().clearCookies();
444
+ // 2. Navigate directly to /polkphpapplicant/s/enrollments?language=en_US.
445
+ await applicantPage.gotoURL(MY_MEDICAL_COVERAGE_PATH, { handleConsentUpdate: false });
446
+ // 3. Wait for navigation and verify redirect/login form.
447
+ await expect.soft(page).toHaveURL(/\/polkphpapplicant\/s\/login/i);
448
+ expect.soft(page.url()).toContain('ec=302');
449
+ expect.soft(page.url()).toContain('startURL');
450
+ expect.soft(page.url()).toContain('enrollments');
451
+ await expect.soft(applicantPage.UsernameField_Input).toBeVisible();
452
+ await expect.soft(applicantPage.PasswordField_Input).toBeVisible();
453
+ await expect.soft(applicantPage.LoginSubmit_Btn).toBeVisible();
454
+ await expect.soft(applicantPage.ForgotPassword_Lnk).toBeVisible();
455
+ await expect.soft(applicantPage.SignUp_Lnk).toBeVisible();
456
+ });
457
+
458
+ test('TC-30 - Login Redirect Returns To My Medical Coverage @no-auth', async ({ page }) => {
459
+ test.skip(!testConfig.login || !testConfig.password, 'LOGIN and PASSWORD must be set for redirect login coverage.');
460
+ // 1. Navigate directly to My Medical Coverage without auth.
461
+ await applicantPage.gotoURL(MY_MEDICAL_COVERAGE_PATH, { handleConsentUpdate: false });
462
+ await expect.soft(page).toHaveURL(/\/polkphpapplicant\/s\/login/i);
463
+ // 2. Complete login with valid credentials.
464
+ await applicantPage.loginAsApplicant(testConfig.login, testConfig.password);
465
+ // 3. Wait for post-login navigation.
466
+ await applicantPage.waitForMyMedicalCoveragePageReady();
467
+ expect.soft(page.url()).toContain('/polkphpapplicant/s/enrollments');
468
+ });
469
+ });
470
+ });
@@ -0,0 +1,188 @@
1
+ // spec: specs/Applicant Privacy Policy Page Test Plan.md
2
+ // seed: seed.spec.ts
3
+
4
+ import { test, expect } from '@playwright/test';
5
+ import { ApplicantPage } from '@pages/ApplicantPage';
6
+ import { WebActions } from '@lib/WebActions';
7
+ import {
8
+ expectApplicantAuthenticatedHeaderFooterVisible,
9
+ expectApplicantAuthenticatedHeaderVisible,
10
+ expectApplicantFooterVisible,
11
+ } from '@lib/ApplicantPortalAssertions';
12
+ import privacyPolicyTextData from '../test-data/privacyPolicy/textContent.json';
13
+
14
+ test.describe('Applicant Privacy Policy Page Tests', () => {
15
+ let applicantPage: ApplicantPage;
16
+ let webActions: WebActions;
17
+
18
+ test.beforeEach(async ({ page }) => {
19
+ applicantPage = new ApplicantPage(page);
20
+ webActions = new WebActions(page, page.context());
21
+ await applicantPage.gotoURL('/polkphpapplicant/s/?language=en_US');
22
+ await webActions.waitForElementAttached(applicantPage.Logout_Btn);
23
+ // Click the Privacy Policy link in the header navigation
24
+ await applicantPage.clickPrivacyPolicyHeaderLink();
25
+ await webActions.waitForElementAttached(applicantPage.PrivacyPolicyTitle_Text);
26
+ });
27
+
28
+ // TC-01 — Authenticated Header, Footer, And Privacy Policy H1 Are Visible
29
+ test('TC-01 - Authenticated Header, Footer, And Privacy Policy H1 Are Visible @auth', async ({ page }) => {
30
+ // Verify authenticated header and footer elements are visible
31
+ await expectApplicantAuthenticatedHeaderFooterVisible(applicantPage);
32
+ // Verify the Privacy Policy H1 heading is visible
33
+ await expect.soft(applicantPage.PrivacyPolicyTitle_Text).toBeVisible();
34
+ });
35
+
36
+ // TC-02 — Registration Information Section Content Is Correct
37
+ test('TC-02 - Registration Information Section Content Is Correct @auth', async ({ page }) => {
38
+ // Verify the Registration Information H2 heading is visible and text matches data
39
+ await expect.soft(applicantPage.RegistrationInformationHeading_Text).toBeVisible();
40
+ expect.soft(await applicantPage.RegistrationInformationHeading_Text.textContent()).toContain(privacyPolicyTextData.h2RegistrationInfo);
41
+ // Verify first paragraph text under Registration Information matches the full expected text
42
+ await expect.soft(applicantPage.PrivacyPolicyFirstParagraph_Text).toBeVisible();
43
+ expect.soft(await applicantPage.PrivacyPolicyFirstParagraph_Text.textContent()).toContain(privacyPolicyTextData.firstParagraph);
44
+ // Verify the SMS Privacy Notice paragraph text matches the full expected text
45
+ await expect.soft(applicantPage.PrivacyPolicySMSNoticeParagraph_Text).toBeVisible();
46
+ expect.soft(await applicantPage.PrivacyPolicySMSNoticeParagraph_Text.textContent()).toContain(privacyPolicyTextData.smsNoticeParagraph);
47
+ });
48
+
49
+ // TC-03 — How Polk County Uses Your Information Section Content Is Correct
50
+ test('TC-03 - How Polk County Uses Your Information Section Content Is Correct @auth', async ({ page }) => {
51
+ // Verify the How Polk County Uses Your Information H2 heading text matches data
52
+ await expect.soft(applicantPage.HowPolkCountyUsesYourInformationHeading_Text).toBeVisible();
53
+ expect.soft(await applicantPage.HowPolkCountyUsesYourInformationHeading_Text.textContent()).toContain(privacyPolicyTextData.h2HowPolkUses);
54
+ // Verify the introductory paragraph text matches data
55
+ await expect.soft(applicantPage.PrivacyPolicyHowPolkIntroParagraph_Text).toBeVisible();
56
+ expect.soft(await applicantPage.PrivacyPolicyHowPolkIntroParagraph_Text.textContent()).toContain(privacyPolicyTextData.howPolkIntroParagraph);
57
+ // Verify the bulleted list has exactly the expected number of items
58
+ await expect(applicantPage.PrivacyPolicyListItems_Li).toHaveCount(privacyPolicyTextData.listItemsCount);
59
+ // Validate every list item's text matches the expected content from the data file
60
+ for (let i = 0; i < privacyPolicyTextData.listItems.length; i++) {
61
+ expect.soft(await applicantPage.PrivacyPolicyListItems_Li.nth(i).textContent()).toContain(privacyPolicyTextData.listItems[i]);
62
+ }
63
+ // Verify the closing paragraph text matches the full expected text
64
+ await expect.soft(applicantPage.PrivacyPolicyClosingParagraph_Text).toBeVisible();
65
+ expect.soft(await applicantPage.PrivacyPolicyClosingParagraph_Text.textContent()).toContain(privacyPolicyTextData.closingParagraph);
66
+ });
67
+
68
+ // TC-04 — Browser Tab Title Is Privacy Policy
69
+ test('TC-04 - Browser Tab Title Is Privacy Policy @auth', async ({ page }) => {
70
+ // Verify the browser tab title matches the expected document title
71
+ await expect(page).toHaveTitle(privacyPolicyTextData.documentTitle);
72
+ });
73
+
74
+ // TC-05 — Heading Hierarchy And Accessibility Attributes Are Correct
75
+ test('TC-05 - Heading Hierarchy And Accessibility Attributes Are Correct @auth', async ({ page }) => {
76
+ // Verify exactly one H1 element with the correct text
77
+ await expect(applicantPage.PrivacyPolicyH1_All).toHaveCount(1);
78
+ await expect(applicantPage.PrivacyPolicyH1Role_Heading).toHaveText(privacyPolicyTextData.h1Title);
79
+ // Verify exactly two H2 elements in the page
80
+ await expect(applicantPage.PrivacyPolicyH2_All).toHaveCount(2);
81
+ // Verify no H3 or lower-level headings are present
82
+ await expect(applicantPage.PrivacyPolicyH3_All).toHaveCount(0);
83
+ // Verify the HTML lang attribute matches data
84
+ await expect(applicantPage.HtmlRoot_Elem).toHaveAttribute('lang', privacyPolicyTextData.htmlLang);
85
+ // Verify the Logo image alt attribute matches data
86
+ await expect(applicantPage.Logo_Img).toHaveAttribute('alt', privacyPolicyTextData.logoAlt);
87
+ });
88
+
89
+ // TC-06 — Privacy Policy Header Link From Another Authenticated Page Navigates To Privacy Policy
90
+ test('TC-06 - Privacy Policy Header Link From Another Authenticated Page Navigates To Privacy Policy @auth', async ({ page }) => {
91
+ // Navigate to the Resources page from the current Privacy Policy page
92
+ await applicantPage.clickResourcesLink();
93
+ await webActions.waitForElementAttached(applicantPage.ResourcesTitle_Text);
94
+ // Verify the Resources page is loaded while user remains authenticated
95
+ await expect.soft(applicantPage.Logout_Btn).toBeVisible();
96
+ // Click the Privacy Policy link in the header navigation
97
+ await applicantPage.clickPrivacyPolicyHeaderLink();
98
+ await webActions.waitForElementAttached(applicantPage.PrivacyPolicyTitle_Text);
99
+ // Verify the browser navigated to the Privacy Policy page
100
+ expect.soft(page.url()).toContain('privacy-policy');
101
+ // Verify the Privacy Policy H1 heading is visible
102
+ await expect.soft(applicantPage.PrivacyPolicyTitle_Text).toBeVisible();
103
+ // Verify user remains authenticated
104
+ await expect.soft(applicantPage.Logout_Btn).toBeVisible();
105
+ });
106
+
107
+ // TC-07 — Header Nav Links From Privacy Policy Page Return To Dashboard While Authenticated
108
+ test('TC-07 - Header Nav Links From Privacy Policy Page Return To Dashboard While Authenticated @auth', async ({ page }) => {
109
+ // Click the Home link in the header navigation
110
+ await applicantPage.clickHomeLink();
111
+ await webActions.waitForElementAttached(applicantPage.WelcomeHeading_Text);
112
+ // Verify navigation to the dashboard page
113
+ expect.soft(page.url()).toContain('/polkphpapplicant/s/');
114
+ expect.soft(page.url()).not.toContain('privacy-policy');
115
+ // Verify the welcome heading is visible
116
+ await expect.soft(applicantPage.WelcomeHeading_Text).toBeVisible();
117
+ // Verify user remains authenticated
118
+ await expect.soft(applicantPage.Logout_Btn).toBeVisible();
119
+ // Navigate back to Privacy Policy and click the Logo image
120
+ await applicantPage.clickPrivacyPolicyHeaderLink();
121
+ await webActions.waitForElementAttached(applicantPage.PrivacyPolicyTitle_Text);
122
+ await applicantPage.Logo_Img.click();
123
+ await webActions.waitForElementAttached(applicantPage.WelcomeHeading_Text);
124
+ // Verify navigation back to dashboard
125
+ expect.soft(page.url()).toContain('/polkphpapplicant/s/');
126
+ expect.soft(page.url()).not.toContain('privacy-policy');
127
+ // Verify user remains authenticated
128
+ await expect.soft(applicantPage.Logout_Btn).toBeVisible();
129
+ });
130
+
131
+ // TC-08 — Desktop Viewport Layout (1500x730) Renders Correctly
132
+ test('TC-08 - Desktop Viewport Layout (1500x730) Renders Correctly @auth', async ({ page }) => {
133
+ // Set the viewport to 1500x730 (desktop) and navigate to Privacy Policy
134
+ await page.setViewportSize({ width: 1500, height: 730 });
135
+ await applicantPage.gotoURL('/polkphpapplicant/s/privacy-policy');
136
+ await webActions.waitForElementAttached(applicantPage.PrivacyPolicyTitle_Text);
137
+ // Verify the Privacy Policy H1 heading is visible
138
+ await expect.soft(applicantPage.PrivacyPolicyTitle_Text).toBeVisible();
139
+ // Verify header & footer elements are visible
140
+ await expectApplicantAuthenticatedHeaderFooterVisible(applicantPage);
141
+ // Verify no horizontal overflow (allow small tolerance for scrollbar width)
142
+ await expect
143
+ .poll(() => page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth + 20))
144
+ .toBeTruthy();
145
+ });
146
+
147
+ // TC-09 — Mobile Viewport Layout (Pixel 4a 412x732) Renders Correctly
148
+ test('TC-09 - Mobile Viewport Layout (Pixel 4a 412x732) Renders Correctly @auth', async ({ page }) => {
149
+ // Set the viewport to 412x732 (Pixel 4a mobile) and navigate to Privacy Policy
150
+ await page.setViewportSize({ width: 412, height: 732 });
151
+ await applicantPage.gotoURL('/polkphpapplicant/s/privacy-policy');
152
+ await webActions.waitForElementAttached(applicantPage.PrivacyPolicyTitle_Text);
153
+ // Verify the Privacy Policy H1 heading is visible
154
+ await expect.soft(applicantPage.PrivacyPolicyTitle_Text).toBeVisible();
155
+ // Verify the Logo image is visible
156
+ await expect.soft(applicantPage.Logo_Img).toBeVisible();
157
+ // Verify the Logout button is visible
158
+ await expect.soft(applicantPage.Logout_Btn).toBeVisible();
159
+ // Scroll to footer and verify footer content
160
+ await applicantPage.PolkHealthCare_Img.scrollIntoViewIfNeeded();
161
+ await expect.soft(applicantPage.PolkHealthCare_Img).toBeVisible();
162
+ await expect.soft(applicantPage.Copyright_Text).toBeVisible();
163
+ });
164
+
165
+ // TC-10 — SMS Privacy Notice Bold Lead-In Formatting Is Present
166
+ test('TC-10 - SMS Privacy Notice Bold Lead-In Formatting Is Present @auth', async ({ page }) => {
167
+ // Locate the SMS Privacy Notice <strong> element and verify it is visible
168
+ await expect.soft(applicantPage.PrivacyPolicySMSNoticeBold_Lbl).toBeVisible();
169
+ // Verify the bold label text matches the expected SMS Privacy Notice label from data
170
+ await expect.soft(applicantPage.PrivacyPolicySMSNoticeBold_Lbl).toHaveText(privacyPolicyTextData.smsNoticeBoldLabel);
171
+ });
172
+
173
+ // TC-11 — No Broken Images On The Privacy Policy Page
174
+ test('TC-11 - No Broken Images On The Privacy Policy Page @auth', async ({ page }) => {
175
+ // Check the header Logo image has loaded correctly
176
+ await applicantPage.Logo_Img.scrollIntoViewIfNeeded();
177
+ await expect.soft(applicantPage.Logo_Img).toBeVisible();
178
+ await expect
179
+ .poll(() => applicantPage.Logo_Img.evaluate((img) => (img as HTMLImageElement).naturalWidth))
180
+ .toBeGreaterThan(0);
181
+ // Scroll to footer and check the Polk HealthCare Plan image has loaded correctly
182
+ await applicantPage.PolkHealthCare_Img.scrollIntoViewIfNeeded();
183
+ await expect.soft(applicantPage.PolkHealthCare_Img).toBeVisible();
184
+ await expect
185
+ .poll(() => applicantPage.PolkHealthCare_Img.evaluate((img) => (img as HTMLImageElement).naturalWidth))
186
+ .toBeGreaterThan(0);
187
+ });
188
+ });