@anddone/paymentlinkportaltestautomation 1.1.1

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.
@@ -0,0 +1,100 @@
1
+ name: Release PaymentLinkPortalTestAutomation
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ release_type:
7
+ description: "Release type (semantic versioning)"
8
+ required: true
9
+ default: patch
10
+ type: choice
11
+ options:
12
+ - patch
13
+ - minor
14
+ - major
15
+
16
+ jobs:
17
+ release:
18
+ runs-on: ubuntu-latest
19
+
20
+ steps:
21
+ # 1. Checkout repo
22
+ - uses: actions/checkout@v4
23
+ with:
24
+ fetch-depth: 0
25
+
26
+ # 2. Setup Node
27
+ - uses: actions/setup-node@v4
28
+ with:
29
+ node-version: 18
30
+ registry-url: https://registry.npmjs.org/
31
+
32
+ # 3. Install dependencies
33
+ - run: npm install
34
+
35
+ # 4. Build project
36
+ - run: npm run build
37
+
38
+ # 5. Configure Git for tags/commits
39
+ - name: Configure Git
40
+ run: |
41
+ git config user.name "github-actions[bot]"
42
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
43
+
44
+ # 6. Bump version safely
45
+ - name: Bump version
46
+ run: |
47
+ # Get release type (patch/minor/major)
48
+ RELEASE_TYPE=${{ inputs.release_type }}
49
+
50
+ # Get current version
51
+ CURRENT_VERSION=$(node -p "require('./package.json').version")
52
+
53
+ # Calculate new version without creating a tag yet
54
+ NEW_VERSION=$(npm version $RELEASE_TYPE --no-git-tag-version)
55
+
56
+ echo "Current version: $CURRENT_VERSION"
57
+ echo "New version: $NEW_VERSION"
58
+
59
+ # Commit package.json changes
60
+ git add package.json
61
+ git commit -m "chore(release): $NEW_VERSION"
62
+
63
+ # Delete tag if exists (avoid errors in CI)
64
+ if git rev-parse "v$NEW_VERSION" >/dev/null 2>&1; then
65
+ git tag -d "v$NEW_VERSION"
66
+ git push origin :refs/tags/v$NEW_VERSION
67
+ fi
68
+
69
+ # Create new tag
70
+ git tag "v$NEW_VERSION"
71
+
72
+ # Push commit + tag
73
+ git push origin master --follow-tags
74
+
75
+ # 7. Publish to npm
76
+ - name: Publish to npm
77
+ run: npm publish --access public
78
+ env:
79
+ NODE_AUTH_TOKEN: ${{ secrets.TESTAUTOMATIONAUTH }}
80
+
81
+ # 8. Slack Success Notification
82
+ - name: Slack Success
83
+ if: success()
84
+ run: |
85
+ PACKAGE_NAME="@anddone/PaymentLinkPortalTestAutomation"
86
+ VERSION=$(node -p "require('./package.json').version")
87
+ curl -X POST -H 'Content-type: application/json' \
88
+ --data "{
89
+ \"username\": \"PaymentLinkPortalTestAutomation\",
90
+ \"text\": \":white_check_mark: npm package published!\n*Package:* $PACKAGE_NAME\n*Version:* $VERSION\n*Release Notes:*\n• Updated Admin page \n• Updated Admin page Names\"
91
+ }" \
92
+ ${{ secrets.SLACK_WEBHOOK_URL }}
93
+
94
+ # 9. Slack Failure Notification
95
+ - name: Slack Failure
96
+ if: failure()
97
+ run: |
98
+ curl -X POST -H 'Content-type: application/json' \
99
+ --data '{"text":":x: npm publish failed. Check GitHub Actions logs."}' \
100
+ ${{ secrets.SLACK_WEBHOOK_URL }}
@@ -0,0 +1,65 @@
1
+ name: Playwright Tests
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ env:
7
+ description: "Environment"
8
+ required: true
9
+ default: QAT
10
+ type: choice
11
+ options:
12
+ - QAT
13
+ - UAT
14
+
15
+ jobs:
16
+ test:
17
+ runs-on: ubuntu-latest
18
+
19
+ environment: ${{ inputs.env }}
20
+
21
+ steps:
22
+ - name: Checkout repo
23
+ uses: actions/checkout@v4
24
+
25
+ - name: Setup Node
26
+ uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 20
29
+
30
+ - name: Install deps
31
+ run: npm ci
32
+
33
+ - name: Map secrets based on ENV
34
+ run: |
35
+ echo "ENV=${{ inputs.env }}" >> $GITHUB_ENV
36
+
37
+ if [ "${{ inputs.env }}" = "UAT" ]; then
38
+ echo "BASE_URL=${{ secrets.BASE_URL_UAT }}" >> $GITHUB_ENV
39
+ echo "MERCHANT_PORTAL_URL_UAT=${{secrets.MERCHANT_PORTAL_URL_UAT}}" >> $GITHUB_ENV
40
+
41
+ else
42
+ echo "BASE_URL=${{ secrets.BASE_URL_QAT }}" >> $GITHUB_ENV
43
+ echo "MERCHANT_PORTAL_URL_QAT=${{secrets.MERCHANT_PORTAL_URL_QAT}}" >> $GITHUB_ENV
44
+ fi
45
+
46
+ - name: Install Playwright Browsers
47
+ run: npx playwright install --with-deps
48
+
49
+ - name: Run Playwright tests
50
+ run: npx playwright test
51
+ env:
52
+ DOTENVX_QUIET: true
53
+ ACH_ACCOUNT_HOLDER: ${{ secrets.ACH_ACCOUNT_HOLDER }}
54
+ ACH_ROUTING_NUMBER: ${{ secrets.ACH_ROUTING_NUMBER }}
55
+ ACH_ACCOUNT_NUMBER: ${{ secrets.ACH_ACCOUNT_NUMBER }}
56
+
57
+ CC_NAME: ${{ secrets.CC_NAME }}
58
+ CC_NUMBER: ${{ secrets.CC_NUMBER }}
59
+ CC_EXPIRY: ${{ secrets.CC_EXPIRY }}
60
+ CC_CVV: ${{ secrets.CC_CVV }}
61
+
62
+ DC_NAME: ${{ secrets.DC_NAME }}
63
+ DC_NUMBER: ${{ secrets.DC_NUMBER }}
64
+ DC_EXPIRY: ${{ secrets.DC_EXPIRY }}
65
+ DC_CVV: ${{ secrets.DC_CVV }}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@anddone/paymentlinkportaltestautomation",
3
+ "version": "1.1.1",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "clean": "rimraf dist",
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/AndDone-LLC/PaymentLinkPortalTestAutomation.git"
14
+ },
15
+ "keywords": [],
16
+ "author": "",
17
+ "license": "ISC",
18
+ "type": "commonjs",
19
+ "bugs": {
20
+ "url": "https://github.com/AndDone-LLC/PaymentLinkPortalTestAutomation/issues"
21
+ },
22
+ "homepage": "https://github.com/AndDone-LLC/PaymentLinkPortalTestAutomation#readme",
23
+ "devDependencies": {
24
+ "@playwright/test": "^1.58.1",
25
+ "@types/dotenv": "^6.1.1",
26
+ "@types/node": "^25.2.0"
27
+ },
28
+ "dependencies": {
29
+ "@anddone/coretestautomation": "^1.0.4",
30
+ "dotenv": "^17.2.4"
31
+ }
32
+ }
@@ -0,0 +1,79 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ /**
4
+ * Read environment variables from file.
5
+ * https://github.com/motdotla/dotenv
6
+ */
7
+ import dotenv from 'dotenv';
8
+ import path from 'path';
9
+ dotenv.config({ path: path.resolve(__dirname, '.env') });
10
+
11
+ /**
12
+ * See https://playwright.dev/docs/test-configuration.
13
+ */
14
+ export default defineConfig({
15
+ testDir: './tests',
16
+ /* Run tests in files in parallel */
17
+ fullyParallel: true,
18
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
19
+ forbidOnly: !!process.env.CI,
20
+ /* Retry on CI only */
21
+ retries: process.env.CI ? 2 : 0,
22
+ /* Opt out of parallel tests on CI. */
23
+ workers: process.env.CI ? 1 : undefined,
24
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
25
+ reporter: 'html',
26
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
27
+ use: {
28
+ /* Base URL to use in actions like `await page.goto('')`. */
29
+ // baseURL: 'http://localhost:3000',
30
+
31
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
32
+ trace: 'on-first-retry',
33
+ },
34
+
35
+ /* Configure projects for major browsers */
36
+ projects: [
37
+ {
38
+ name: 'chromium',
39
+ use: { ...devices['Desktop Chrome'] },
40
+ },
41
+
42
+ // {
43
+ // name: 'firefox',
44
+ // use: { ...devices['Desktop Firefox'] },
45
+ // },
46
+
47
+ // {
48
+ // name: 'webkit',
49
+ // use: { ...devices['Desktop Safari'] },
50
+ // },
51
+
52
+ /* Test against mobile viewports. */
53
+ // {
54
+ // name: 'Mobile Chrome',
55
+ // use: { ...devices['Pixel 5'] },
56
+ // },
57
+ // {
58
+ // name: 'Mobile Safari',
59
+ // use: { ...devices['iPhone 12'] },
60
+ // },
61
+
62
+ /* Test against branded browsers. */
63
+ // {
64
+ // name: 'Microsoft Edge',
65
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
66
+ // },
67
+ // {
68
+ // name: 'Google Chrome',
69
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
70
+ // },
71
+ ],
72
+
73
+ /* Run your local dev server before starting the tests */
74
+ // webServer: {
75
+ // command: 'npm run start',
76
+ // url: 'http://localhost:3000',
77
+ // reuseExistingServer: !process.env.CI,
78
+ // },
79
+ });
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./pages/PaymentLinkPortalLandingPage";
2
+ export * from "./pages/receiptPage";
3
+ export * from "./pages/summaryPage";
@@ -0,0 +1,477 @@
1
+ import { Page, Locator, expect } from "@playwright/test";
2
+ import { CommonUtils } from '@anddone/coretestautomation/dist';
3
+
4
+ export class PaymentLinkPortalLandingPage {
5
+ readonly page: Page;
6
+
7
+ //-----LEFT SUMMARY PANEL----
8
+ readonly merchantName: Locator;
9
+ readonly shopperName: Locator;
10
+ readonly transactionAmount: Locator;
11
+ readonly merchantReference: Locator;
12
+ readonly accountNumber: Locator;
13
+ readonly purposeOfPayment: Locator;
14
+
15
+ //-----CONTACT INFO-----
16
+ readonly companyNameInput: Locator;
17
+ readonly firstNameInput: Locator;
18
+ readonly lastNameInput: Locator;
19
+ readonly emailInput: Locator;
20
+ readonly phoneInput: Locator;
21
+
22
+ //-----PAYMENT METHOD
23
+ readonly achOption: Locator;
24
+ readonly cardOption: Locator;
25
+ readonly achSameNameCheckbox: Locator;
26
+ readonly cardSameNameCheckbox: Locator;
27
+
28
+ //-----ACH FIELDS----
29
+ readonly routingNumberInput: Locator;
30
+ readonly accountNumberInput: Locator;
31
+ readonly verifyAccountNumberInput: Locator;
32
+ readonly bankNameInput: Locator;
33
+ readonly accountHolderName: Locator;
34
+
35
+ //------CARD FIELDS-----
36
+ readonly cardNumberInput: Locator;
37
+ readonly expiryInput: Locator;
38
+ readonly cvvInput: Locator;
39
+ readonly cardHolderName: Locator;
40
+
41
+ //-----=====BILLING ADDRESS
42
+ readonly billingAddressInput: Locator;
43
+ readonly billingSuiteInput: Locator;
44
+ readonly billingCityInput: Locator;
45
+ readonly billingCountrySelect: Locator;
46
+ readonly billingStateSelect: Locator;
47
+ readonly billingPostalCodeInput: Locator;
48
+
49
+ //-----PRICE SUMMARY-----
50
+ readonly totalAmountValue: Locator;
51
+
52
+ //-----SUBMIT BUTTON-----
53
+ readonly submitButton: Locator;
54
+
55
+ constructor(page: Page) {
56
+ this.page = page;
57
+
58
+ // ----- CONTACT INFO -----
59
+ this.companyNameInput = page.locator('#inputCompanyName');
60
+ this.firstNameInput = page.locator('#inputFirstName');
61
+ this.lastNameInput = page.locator('#inputLastName');
62
+ this.emailInput = page.locator('#inputEmail');
63
+ this.phoneInput = page.locator('#inputPhone');
64
+
65
+ // ----- LEFT PANEL -----
66
+ this.merchantName = page.locator('.pflite-business-name');
67
+ this.shopperName = page.locator('#named-insured-value');
68
+ this.transactionAmount = page.locator('[id*="transaction-total"]');
69
+ this.merchantReference = page.locator('#merchantRef');
70
+ this.accountNumber = page.locator('#referenceValue1');
71
+ this.purposeOfPayment = page.locator('#output-purpose');
72
+
73
+ // ----- PAYMENT METHOD -----
74
+ this.achOption = page.locator('#pay-ach');
75
+ this.cardOption = page.locator('#pay-card');
76
+ this.achSameNameCheckbox = page.locator('#ach-name-copy');
77
+ this.cardSameNameCheckbox = page.locator('#card-name-copy');
78
+
79
+ // ----- ACH DETAILS -----
80
+ this.routingNumberInput = page.locator('#inputRouting');
81
+ this.accountNumberInput = page.locator('#bankAccNum');
82
+ this.verifyAccountNumberInput = page.locator('#inputBankAcc2');
83
+ this.bankNameInput = page.locator('#bankName');
84
+ this.accountHolderName = page.locator(('#inputAccName'));
85
+
86
+ // -----CARD DETAILS -----
87
+ this.cardNumberInput = page.locator('#cardNumber');
88
+ this.expiryInput = page.locator('#inputExp');
89
+ this.cvvInput = page.locator('#inputSec');
90
+ this.cardHolderName = page.locator('#nameOnCard');
91
+
92
+
93
+ // ----- BILLING ADDRESS -----
94
+ this.billingAddressInput = page.locator('input[formcontrolname="StreetNumber"]');
95
+ this.billingSuiteInput = page.locator('input[formcontrolname="Street"]');
96
+ this.billingCityInput = page.locator('input[formcontrolname="City"]');
97
+ this.billingCountrySelect = page.locator('select[formcontrolname="Country"]');
98
+ this.billingStateSelect = page.locator('select[formcontrolname="State"]');
99
+ this.billingPostalCodeInput = page.locator('#inBillingZipCodePayLink');
100
+
101
+ // ----- PRICE -----
102
+ this.totalAmountValue = page.locator('#totalAmountValue');
103
+
104
+ // ----- SUBMIT -----
105
+ this.submitButton = page.locator('.confirm-button');
106
+
107
+ }
108
+
109
+ // ================= CONTACT INFO ACTIONS =================
110
+ async fillCompanyName(company: string) {
111
+ await CommonUtils.fill(this.companyNameInput, company, {
112
+ description: 'Company Name Input'
113
+ });
114
+ }
115
+
116
+ async fillFirstName(first: string) {
117
+ await CommonUtils.fill(this.firstNameInput, first, {
118
+ description: 'First Name Input'
119
+ });
120
+ }
121
+
122
+ async fillLastName(last: string) {
123
+ await CommonUtils.fill(this.lastNameInput, last, {
124
+ description: 'Last Name Input'
125
+ });
126
+ }
127
+
128
+ async fillEmail(email: string) {
129
+ await CommonUtils.fill(this.emailInput, email, {
130
+ description: 'Email Input'
131
+ });
132
+ }
133
+
134
+ async fillPhone(phone: string) {
135
+ await CommonUtils.fill(this.phoneInput, phone, {
136
+ description: 'Phone Input'
137
+ });
138
+ }
139
+ async fillContactInfo(
140
+ options: { first?: string; last?: string; email?: string; phone?: string; company?: string; }
141
+ ): Promise<void> {
142
+
143
+ if (options.company) {
144
+ await this.fillCompanyName(options.company);
145
+ }
146
+
147
+ if (options.first) {
148
+ await this.fillFirstName(options.first);
149
+ }
150
+
151
+ if (options.last) {
152
+ await this.fillLastName(options.last);
153
+ }
154
+
155
+ if (options.email) {
156
+ await this.fillEmail(options.email);
157
+ }
158
+
159
+ if (options.phone) {
160
+ await this.fillPhone(options.phone);
161
+ }
162
+ }
163
+
164
+ // ================= LEFT PANEL GETTERS =================
165
+
166
+ async getShopperName(): Promise<string> {
167
+ return await CommonUtils.getText(this.shopperName);
168
+ }
169
+
170
+ async getTransactionAmount(): Promise<string> {
171
+ return await CommonUtils.getText(this.transactionAmount);
172
+ }
173
+
174
+ async getMerchantReference(): Promise<string> {
175
+ return await CommonUtils.getText(this.merchantReference);
176
+ }
177
+
178
+ async getAccountNumber(): Promise<string> {
179
+ return await CommonUtils.getText(this.accountNumber);
180
+ }
181
+
182
+ async getPurposeOfPayment(): Promise<string> {
183
+ return await CommonUtils.getText(this.purposeOfPayment);
184
+ }
185
+ // ================= PAYMENT METHOD =================
186
+ private currentPaymentMethod: 'ACH' | 'CARD' = 'ACH';
187
+
188
+ async selectPaymentMethod(method: 'ACH' | 'CARD'): Promise<void> {
189
+ const option =
190
+ method === 'ACH'
191
+ ? this.achOption
192
+ : this.cardOption;
193
+
194
+
195
+ await CommonUtils.waitSeconds(5);
196
+ await CommonUtils.waitForVisible(option);
197
+ await CommonUtils.click(option, {
198
+ description: `${method} Payment Option`
199
+ });
200
+ this.currentPaymentMethod = method;
201
+ }
202
+
203
+ // ================= CHECKBOX METHOD =================
204
+
205
+ async waitForValueToStabilize(locator: Locator, checks = 3, gapMs = 200) {
206
+ let prev = await locator.inputValue();
207
+
208
+ for (let i = 0; i < checks; i++) {
209
+ await locator.page().waitForTimeout(gapMs);
210
+ const next = await locator.inputValue();
211
+ if (next !== prev) {
212
+ prev = next;
213
+ i = -1;
214
+ }
215
+ }
216
+ }
217
+ async checkOrUncheckCheckbox(shouldBeChecked: boolean) {
218
+ const checkbox =
219
+ this.currentPaymentMethod === 'ACH'
220
+ ? this.achSameNameCheckbox
221
+ : this.cardSameNameCheckbox;
222
+
223
+ const targetField =
224
+ this.currentPaymentMethod === 'ACH'
225
+ ? this.accountHolderName
226
+ : this.cardHolderName;
227
+
228
+ const isChecked = await CommonUtils.isChecked(checkbox);
229
+
230
+ if (isChecked !== shouldBeChecked) {
231
+ await CommonUtils.click(checkbox, {
232
+ description: `${this.currentPaymentMethod} Same Name Checkbox`
233
+ });
234
+
235
+ if (!shouldBeChecked) {
236
+ await this.page.waitForTimeout(500);
237
+ }
238
+
239
+ await expect(checkbox).toHaveJSProperty('checked', shouldBeChecked);
240
+ await this.waitForValueToStabilize(targetField);
241
+ }
242
+ }
243
+
244
+
245
+ async fillAccountHolderName(accountHolderName: string) {
246
+ await CommonUtils.fill(this.accountHolderName, accountHolderName, {
247
+ description: 'Account Holder Name Input'
248
+ });
249
+ }
250
+
251
+ async fillCardHolderName(cardHolderName: string): Promise<void> {
252
+ await CommonUtils.waitSeconds(2);
253
+ await CommonUtils.waitForVisible(this.cardHolderName, {
254
+ description: 'Card Holder Name Input'
255
+ });
256
+
257
+ await CommonUtils.fill(this.cardHolderName, cardHolderName, {
258
+ description: 'Card Holder Name Input'
259
+ });
260
+
261
+ await this.cardHolderName.press('Tab');
262
+ }
263
+
264
+
265
+ // ================= ACH DETAILS =================
266
+
267
+ async clickRoutingNumberInput(): Promise<void> {
268
+ await CommonUtils.click(this.routingNumberInput, {
269
+ description: 'Routing Number Input'
270
+ });
271
+ }
272
+
273
+ async typeRoutingNumberInput(routingNumber: string) {
274
+ await CommonUtils.typeText(this.routingNumberInput, routingNumber, {
275
+ description: 'Bank Routing Number Input'
276
+ });
277
+ }
278
+
279
+ async typeBankAccountNumberInput(account: string): Promise<void> {
280
+ await CommonUtils.typeText(this.accountNumberInput, account, {
281
+ description: 'Bank Account Number Input'
282
+ });
283
+ }
284
+
285
+ async typeVerifyBankAccountNumberInput(account: string): Promise<void> {
286
+ await CommonUtils.typeText(this.verifyAccountNumberInput, account, {
287
+ description: 'Verify Bank Account Number Input'
288
+ });
289
+ }
290
+
291
+ async fillACHDetails(
292
+ options: { routing?: string; account?: string; }
293
+ ): Promise<void> {
294
+
295
+ if (options.routing) {
296
+ await this.clickRoutingNumberInput();
297
+ await this.typeRoutingNumberInput(options.routing);
298
+ }
299
+
300
+ if (options.account) {
301
+ await this.typeBankAccountNumberInput(options.account);
302
+ await this.typeVerifyBankAccountNumberInput(options.account);
303
+ }
304
+ }
305
+
306
+ // ================= CARD DETAILS =================
307
+
308
+ async typeCardNumber(cardNum: string): Promise<void> {
309
+ // await CommonUtils.typeText(this.cardNumberInput, cardNum, {
310
+ // description: 'Card Number Input'
311
+ // });
312
+ //await this.secureFill(this.cardNumberInput,cardNum);
313
+ await this.secureType(this.cardNumberInput,cardNum);
314
+ }
315
+ async typeExpiryDate(exp: string): Promise<void> {
316
+ // await CommonUtils.typeText(this.expiryInput, exp, {
317
+ // description: 'Card Expiry Input'
318
+ // });
319
+ // await this.secureFill(this.expiryInput,exp);
320
+ await this.secureType(this.expiryInput,exp);
321
+ }
322
+ async typeCVV(cvv: string): Promise<void> {
323
+ // await CommonUtils.typeText(this.cvvInput, cvv, {
324
+ // description: 'Card CVV Input'
325
+ // });
326
+ // await this.secureFill(this.cvvInput,cvv);
327
+ await this.secureType(this.cvvInput,cvv);
328
+ }
329
+
330
+ async fillCardDetails(
331
+ options: { cardNum?: string; exp?: string; cvv?: string; }
332
+ ): Promise<void> {
333
+
334
+ if (options.cardNum) {
335
+ await this.typeCardNumber(options.cardNum);
336
+ }
337
+
338
+ await CommonUtils.waitSeconds(4);
339
+ if (options.exp) {
340
+ await this.typeExpiryDate(options.exp);
341
+ }
342
+ await CommonUtils.waitSeconds(2);
343
+ if (options.cvv) {
344
+ await this.typeCVV(options.cvv);
345
+ }
346
+ }
347
+
348
+ async fillBillingAddressLine(address: string): Promise<void> {
349
+ await CommonUtils.fill(this.billingAddressInput, address, {
350
+ description: 'Billing Address'
351
+ });
352
+ }
353
+ async fillBillingSuite(suite: string): Promise<void> {
354
+ await CommonUtils.fill(this.billingSuiteInput, suite, {
355
+ description: 'Billing Suite'
356
+ });
357
+ }
358
+ async fillBillingCity(city: string): Promise<void> {
359
+ await CommonUtils.fill(this.billingCityInput, city, {
360
+ description: 'Billing City'
361
+ });
362
+ }
363
+ async selectBillingCountry(country: string): Promise<void> {
364
+ await CommonUtils.selectByText(this.billingCountrySelect, country, {
365
+ description: 'Billing Country'
366
+ });
367
+ }
368
+ async selectBillingState(state: string): Promise<void> {
369
+ await CommonUtils.selectByText(this.billingStateSelect, state, {
370
+ description: 'Billing State'
371
+ });
372
+ }
373
+ async fillBillingPostalCode(postalCode: string): Promise<void> {
374
+ await CommonUtils.fill(this.billingPostalCodeInput, postalCode, {
375
+ description: 'Billing Postal Code'
376
+ });
377
+ }
378
+
379
+ async fillBillingAddress(
380
+ options: {
381
+ address?: string; suite?: string; city?: string; country?: string; state?: string;
382
+ postalCode?: string;
383
+ }
384
+ ): Promise<void> {
385
+
386
+ await CommonUtils.waitForVisible(this.billingAddressInput, {
387
+ description: 'Billing Address Section'
388
+ });
389
+
390
+ if (options.address) {
391
+ await this.fillBillingAddressLine(options.address);
392
+ }
393
+
394
+ if (options.suite) {
395
+ await this.fillBillingSuite(options.suite);
396
+ }
397
+
398
+ if (options.city) {
399
+ await this.fillBillingCity(options.city);
400
+ }
401
+
402
+ if (options.country) {
403
+ await this.selectBillingCountry(options.country);
404
+ }
405
+
406
+ if (options.state) {
407
+ await this.selectBillingState(options.state);
408
+ }
409
+
410
+ if (options.postalCode) {
411
+ await this.fillBillingPostalCode(options.postalCode);
412
+ }
413
+ }
414
+
415
+
416
+ // ================= PRICE =================
417
+ async getTotalAmount(): Promise<string> {
418
+ return await CommonUtils.getText(this.totalAmountValue, {
419
+ description: 'Total Amount Value'
420
+ });
421
+ }
422
+
423
+ // ================= SUBMIT =================
424
+ async submitPayment(): Promise<void> {
425
+ const spinner = this.page.locator('.loading-spinner');
426
+
427
+ await CommonUtils.waitForInvisible(spinner, {
428
+ description: 'Submit Button Spinner'
429
+ });
430
+
431
+ await CommonUtils.click(this.submitButton, {
432
+ description: 'Submit Payment Button'
433
+ });
434
+ }
435
+
436
+ async clickOnCardMethod(): Promise<void> {
437
+ await CommonUtils.click(this.cardOption);
438
+ }
439
+
440
+ async secureType(locator: Locator, value: string): Promise<void> {
441
+
442
+ await locator.waitFor({ state: 'visible' });
443
+
444
+ await locator.evaluate(async (element, val) => {
445
+ const input = element as HTMLInputElement;
446
+
447
+ input.focus();
448
+ input.value = '';
449
+
450
+ for (const char of val) {
451
+
452
+ // Create keyboard events
453
+ const keyDown = new KeyboardEvent('keydown', { key: char, bubbles: true });
454
+ const keyPress = new KeyboardEvent('keypress', { key: char, bubbles: true });
455
+ const keyUp = new KeyboardEvent('keyup', { key: char, bubbles: true });
456
+
457
+ input.dispatchEvent(keyDown);
458
+ input.dispatchEvent(keyPress);
459
+
460
+ // Append character
461
+ input.value += char;
462
+
463
+ // Trigger input event
464
+ input.dispatchEvent(new Event('input', { bubbles: true }));
465
+
466
+ input.dispatchEvent(keyUp);
467
+
468
+ // Small delay to mimic typing speed
469
+ await new Promise(res => setTimeout(res, 50));
470
+ }
471
+
472
+ input.dispatchEvent(new Event('change', { bubbles: true }));
473
+ input.dispatchEvent(new Event('blur', { bubbles: true }));
474
+
475
+ }, value);
476
+ }
477
+ }
@@ -0,0 +1,65 @@
1
+ import { CommonUtils, TableUtils } from "@anddone/coretestautomation/dist";
2
+ import { Page, Locator } from "@playwright/test";
3
+
4
+ export class ReceiptPage {
5
+ readonly page: Page;
6
+ readonly transactionStatus: Locator;
7
+ readonly merchantDbaName: Locator;
8
+ readonly transactionAmount: Locator;
9
+ readonly receiptLabel_list: Locator;
10
+ readonly receiptValue_list: Locator;
11
+ readonly merchantAddress_value: Locator;
12
+ readonly merchantEmail_value: Locator;
13
+ readonly merchantPhone_value: Locator;
14
+ readonly shopperBehalf_value: Locator;
15
+ readonly accountNumber_value: Locator;
16
+ readonly transactionId_value: Locator;
17
+ readonly merchantRef_value: Locator;
18
+ readonly traceNumber_value: Locator;
19
+
20
+ constructor(page: Page) {
21
+ this.page = page;
22
+ this.transactionStatus = page.locator('span.transaction-status');
23
+ this.merchantDbaName = page.locator('#merchant-dba');
24
+ this.transactionAmount = page.locator('#transaction-amount');
25
+ this.receiptLabel_list = page.locator('td.label');
26
+ this.receiptValue_list = page.locator('tr td:not(.label)');
27
+ this.merchantAddress_value = page.locator('#merchant-address');
28
+ this.merchantEmail_value = page.locator('#merchant-email')
29
+ this.merchantPhone_value = page.locator('#merchant-phone');
30
+ this.shopperBehalf_value = page.locator('#shopper-behalf');
31
+ this.accountNumber_value = page.locator('#transaction-invoice');
32
+ this.transactionId_value = page.locator('#transaction-id');
33
+ this.merchantRef_value = page.locator('#transaction-ref');
34
+ this.traceNumber_value = page.locator('#transaction-trace');
35
+ }
36
+
37
+ async getReceiptLabels(): Promise<string[]> {
38
+ return CommonUtils.getAllInnerText(this.receiptLabel_list);
39
+ }
40
+
41
+ async getReceiptLabelValues(): Promise<string[]> {
42
+ return CommonUtils.getAllInnerText(this.receiptValue_list);
43
+ }
44
+
45
+ async getReceiptDataMap(): Promise<Record<string, string>> {
46
+ const labels = await this.getReceiptLabels();
47
+ const values = await this.getReceiptLabelValues();
48
+
49
+ const receiptMap: Record<string, string> = {};
50
+ if (!labels.length || !values.length) {
51
+ console.warn("⚠ Receipt labels or values are empty");
52
+ return receiptMap;
53
+ }
54
+ if (labels.length !== values.length) {
55
+ console.warn(
56
+ `⚠ Label/value count mismatch → labels: ${labels.length}, values: ${values.length}`
57
+ );
58
+ }
59
+ labels.forEach((label, index) => {
60
+ receiptMap[label.trim()] = values[index]?.trim() ?? "";
61
+ });
62
+ return receiptMap;
63
+ }
64
+
65
+ }
@@ -0,0 +1,112 @@
1
+ import { Page, Locator } from "@playwright/test";
2
+ import { CommonUtils } from '@anddone/coretestautomation/dist'
3
+
4
+ export class SummaryPage {
5
+ readonly page: Page;
6
+ readonly businessName: Locator;
7
+ readonly receiptHasBeenSent_txtLine: Locator;
8
+ readonly paymentSummary_label: Locator;
9
+ readonly viewReceipt_icon: Locator;
10
+ readonly transactionId: Locator;
11
+ readonly transactionSummary_label_list: Locator;
12
+ readonly contactInfo_name_value: Locator;
13
+ readonly contactInfo_email_value: Locator;
14
+ readonly contactInfo_phone_value: Locator;
15
+ readonly shopperCompanyName_value: Locator;
16
+ readonly purposeOfPayment_value: Locator;
17
+ readonly paymentMethod_value: Locator;
18
+ readonly lastFourDigitsOfCard:Locator;
19
+ readonly paymentWillAppearAs_value: Locator;
20
+ readonly amount_value: Locator;
21
+ readonly technologyFee_value: Locator;
22
+ readonly paymentTotal_value: Locator;
23
+ readonly footer: Locator;
24
+ readonly privacyPolicy_footer_link: Locator;
25
+ readonly termsOfUse_footer_link: Locator;
26
+
27
+ constructor(page: Page) {
28
+ this.page = page;
29
+ this.businessName = page.locator('h5.business-name')
30
+ this.paymentSummary_label = page.getByText("Payment Summary");
31
+ this.viewReceipt_icon = page.locator('.summary-column #receipt-icon');
32
+ this.transactionId = page.getByText(/Your Transaction ID is:/);
33
+ this.transactionSummary_label_list = page.locator('div.summary-column div.summary-row span.label');
34
+ this.contactInfo_name_value = page.locator('#insured-name');
35
+ this.contactInfo_email_value = page.locator('#insured-email');
36
+ this.contactInfo_phone_value = page.locator('#insured-phone');
37
+ this.shopperCompanyName_value = page.locator('#insured-behalf');
38
+ this.purposeOfPayment_value = page.locator('#insured-purpose');
39
+ this.paymentMethod_value = page.locator('#insured-payMethod');
40
+ this.lastFourDigitsOfCard=page.locator('#insured-lastFour');
41
+ this.paymentWillAppearAs_value = page.locator('#insured-merchant');
42
+ this.amount_value = this.page.locator('#insured-amount');
43
+ this.technologyFee_value = this.page.locator('#insured-fee');
44
+ this.paymentTotal_value = this.page.locator('#insured-total');
45
+ this.footer = page.locator('div.footer');
46
+ this.receiptHasBeenSent_txtLine = page.locator('div.summary-header span');
47
+ this.privacyPolicy_footer_link = page.locator('.links a[href*="/privacy/"]');
48
+ this.termsOfUse_footer_link = page.locator('.links a[href*="/terms/"]')
49
+ }
50
+
51
+ async isTransactionSummaryPageDisplayed(): Promise<boolean> {
52
+ return await CommonUtils.isVisible(this.paymentSummary_label);
53
+ }
54
+
55
+ async getTransactionId(): Promise<string> {
56
+ const text = await CommonUtils.getText(this.transactionId);
57
+ return text?.split("is:")?.[1]?.trim() ?? "";
58
+ }
59
+
60
+ async getSummaryLabels():Promise<string[]>{
61
+ return await CommonUtils.getAllText(this.transactionSummary_label_list);
62
+ }
63
+
64
+ async getNameFromContactInfo():Promise<string>{
65
+ return await CommonUtils.getText(this.contactInfo_name_value,{description:'Name'})
66
+ }
67
+
68
+ async getEmailFromContactInfo():Promise<string>{
69
+ return await CommonUtils.getText(this.contactInfo_email_value,{description:'Email'})
70
+ }
71
+
72
+ async getPhoneFromContactInfo():Promise<string>{
73
+ return await CommonUtils.getText(this.contactInfo_phone_value,{description:'Phone'})
74
+ }
75
+
76
+ async getShopperCompanyName():Promise<string>{
77
+ return await CommonUtils.getText(this.shopperCompanyName_value,{description:'Phone'})
78
+ }
79
+
80
+ async getPurposeOfPayment():Promise<string>{
81
+ return await CommonUtils.getText(this.purposeOfPayment_value,{description:'Purpose of Payment'})
82
+ }
83
+
84
+ async getPaymentMethod():Promise<string>{
85
+ return await CommonUtils.getText(this.paymentMethod_value,{description:'Payment Method'})
86
+ }
87
+
88
+ async getLastFourDigitOfCard():Promise<string>{
89
+ return await CommonUtils.getText(this.lastFourDigitsOfCard)
90
+ }
91
+
92
+ async getPaymentWillAppearAs():Promise<string>{
93
+ return await CommonUtils.getText(this.paymentWillAppearAs_value)
94
+ }
95
+
96
+ async getAmount():Promise<string>{
97
+ return await CommonUtils.getText(this.amount_value)
98
+ }
99
+
100
+ async getTechnologyFee():Promise<string>{
101
+ return await CommonUtils.getText(this.technologyFee_value)
102
+ }
103
+
104
+ async getPaymentTotal():Promise<string>{
105
+ return await CommonUtils.getText(this.paymentTotal_value)
106
+ }
107
+
108
+ async clickOnViewReceiptIcon():Promise<void>{
109
+ await CommonUtils.click(this.viewReceipt_icon)
110
+ }
111
+
112
+ }
@@ -0,0 +1,45 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { PaymentLinkPortalLandingPage } from '../src/pages/PaymentLinkPortalLandingPage';
3
+ import { SummaryPage } from '../src/pages/summaryPage';
4
+ import { AssertionUtils, CommonUtils,PageUtils } from '@anddone/coretestautomation/dist';
5
+
6
+
7
+ test('Complete ACH payment flow', async ({ page }) => {
8
+ await PageUtils.navigateTo(page,'https://link.qat.anddone.com/#/qx7KNjWx1')
9
+
10
+ const paymentPage = new PaymentLinkPortalLandingPage(page);
11
+ const summaryPage = new SummaryPage(page);
12
+
13
+ await paymentPage.fillContactInfo({
14
+ company: 'ABC PVT', first: 'Aditi', last: 'Kadam',
15
+ email: 'abc@gmail.com', phone: '9876543210'
16
+ });
17
+ const value = await paymentPage.getShopperName();
18
+ console.log(value);
19
+
20
+
21
+ await paymentPage.selectPaymentMethod('CARD');
22
+ await paymentPage.checkOrUncheckCheckbox(false);
23
+ await paymentPage.fillCardHolderName('Ron Dixit');
24
+ await paymentPage.fillCardDetails({cardNum: '5555555555554444',exp: '03/30',cvv: '737'
25
+ });
26
+
27
+
28
+ await paymentPage.fillBillingAddress({address: '123 Main Street',suite: 'Apt 10A',
29
+ city: 'New York',country: 'USA',state: 'NY-New York',
30
+ postalCode: '400708456'
31
+ });
32
+
33
+ const totalAmount = await paymentPage.getTotalAmount();
34
+ console.log(totalAmount);
35
+
36
+ await paymentPage.submitPayment();
37
+
38
+ const transactionId=await summaryPage.getTransactionId();
39
+ console.log('Transactio Id: ',transactionId)
40
+ AssertionUtils.verifyNotNull(transactionId);
41
+
42
+ const amount = await summaryPage.getAmount();
43
+ console.log('Amount:',transactionId)
44
+
45
+ });
@@ -0,0 +1,38 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { PaymentLinkPortalLandingPage } from '../src/pages/PaymentLinkPortalLandingPage';
3
+ import { SummaryPage } from '../src/pages/summaryPage';
4
+ import { AssertionUtils, GenerationUtils, PageUtils } from '@anddone/coretestautomation/dist';
5
+
6
+
7
+ test('Complete Cad payment flow', async ({ page }) => {
8
+ await PageUtils.navigateTo(page, 'https://link.qat.anddone.com/#/jxbYaaLv')
9
+
10
+ const paymentPage = new PaymentLinkPortalLandingPage(page);
11
+ const summaryPage = new SummaryPage(page);
12
+
13
+ await paymentPage.selectPaymentMethod('CARD');
14
+
15
+ await paymentPage.fillCardHolderName(GenerationUtils.randomFullName());
16
+ await paymentPage.fillCardDetails({
17
+ cardNum: process.env.CC_NUMBER, exp: process.env.CC_EXPIRY, cvv: process.env.CC_CVV
18
+ });
19
+
20
+ await paymentPage.fillBillingAddress({
21
+ address: GenerationUtils.randomAddressLine(), suite: 'Apt 10A',
22
+ city: 'New York', country: 'USA', state: 'NY-New York',
23
+ postalCode: '12131'
24
+ });
25
+
26
+ const totalAmount = await paymentPage.getTotalAmount();
27
+ console.log(totalAmount);
28
+
29
+ await paymentPage.submitPayment();
30
+
31
+ const transactionId = await summaryPage.getTransactionId();
32
+ console.log('Transactio Id: ', transactionId)
33
+ AssertionUtils.verifyNotNull(transactionId);
34
+
35
+ const amount = await summaryPage.getAmount();
36
+ console.log('Amount:', amount)
37
+
38
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,40 @@
1
+ // {
2
+ // "compilerOptions": {
3
+ // "target": "ESNext",
4
+ // "module": "NodeNext",
5
+ // "moduleResolution": "NodeNext",
6
+
7
+ // "strict": true,
8
+ // "skipLibCheck": true,
9
+
10
+ // "sourceMap": true,
11
+
12
+ // "esModuleInterop": true,
13
+ // "allowSyntheticDefaultImports": true,
14
+
15
+ // "types": ["node", "playwright"],
16
+ // "ignoreDeprecations": "6.0",
17
+
18
+ // "resolveJsonModule": true,
19
+
20
+ // "verbatimModuleSyntax": false,
21
+ // "isolatedModules": false
22
+ // }
23
+ // }
24
+
25
+ {
26
+ "compilerOptions": {
27
+ "target": "ES2020",
28
+ "module": "CommonJS",
29
+ "rootDir": "src",
30
+ "outDir": "dist",
31
+ "declaration": true,
32
+ "declarationMap": true,
33
+ "sourceMap": true,
34
+ "strict": true,
35
+ "esModuleInterop": true,
36
+ "skipLibCheck": true
37
+ },
38
+ "include": ["src/**/*.ts"],
39
+ "exclude": ["node_modules", "dist"]
40
+ }