Skip to content

Commit 728f845

Browse files
authored
pulled numeric and text values into constants (#208)
1 parent 1c68285 commit 728f845

33 files changed

+3289
-4419
lines changed

.github/workflows/android-build-and-test.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,17 @@ jobs:
496496
- name: Checkout code
497497
uses: actions/checkout@v4
498498

499+
- name: Read Appium version from package.json
500+
id: appium-version
501+
env:
502+
DEFAULT_APPIUM_VERSION: ${{ env.DEFAULT_APPIUM_VERSION }}
503+
GITHUB_WORKSPACE: ${{ github.workspace }}
504+
run: |
505+
echo "Reading Appium version from app/package.json"
506+
APP_VER=$(node -e "const p=require(process.env.GITHUB_WORKSPACE + '/app/package.json'); const v=(p.devDependencies&&p.devDependencies.appium)||(p.dependencies&&p.dependencies.appium)||process.env.DEFAULT_APPIUM_VERSION; console.log(String(v).replace(/^[^0-9]*/,''));")
507+
echo "appium_version=$APP_VER" >> $GITHUB_OUTPUT
508+
echo "Detected Appium version: $APP_VER"
509+
499510
# Install phase using composite action
500511
- name: Install Android App
501512
id: install-android

.github/workflows/ios-build-and-test.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,17 @@ jobs:
494494
steps:
495495
- name: Checkout code
496496
uses: actions/checkout@v4
497+
498+
- name: Read Appium version from package.json
499+
id: appium-version
500+
env:
501+
DEFAULT_APPIUM_VERSION: ${{ env.DEFAULT_APPIUM_VERSION }}
502+
GITHUB_WORKSPACE: ${{ github.workspace }}
503+
run: |
504+
echo "Reading Appium version from app/package.json"
505+
APP_VER=$(node -e "const p=require(process.env.GITHUB_WORKSPACE + '/app/package.json'); const v=(p.devDependencies&&p.devDependencies.appium)||(p.dependencies&&p.dependencies.appium)||process.env.DEFAULT_APPIUM_VERSION; console.log(String(v).replace(/^[^0-9]*/,''));")
506+
echo "appium_version=$APP_VER" >> $GITHUB_OUTPUT
507+
echo "Detected Appium version: $APP_VER"
497508
498509
# Install phase using composite action template
499510
- name: Install iOS App

app/package-lock.json

Lines changed: 2728 additions & 4234 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const {
2+
TIMEOUT,
3+
PAUSE,
4+
SCROLL,
5+
GESTURE,
6+
RETRY,
7+
REGEX,
8+
DEFAULTS,
9+
LIMITS,
10+
PLATFORM,
11+
} = require('../pageobjects/utils/constants');
12+
13+
/**
14+
* Global constants fixture for Mocha tests
15+
* Exposes shared test constants under global.constants
16+
*/
17+
18+
/**
19+
* Setup global constants fixture before all tests
20+
*/
21+
exports.mochaGlobalSetup = async function () {
22+
console.info('🔧 Setting up constants global fixture...');
23+
24+
global.constants = {
25+
...(global.constants || {}),
26+
TIMEOUT,
27+
PAUSE,
28+
SCROLL,
29+
GESTURE,
30+
RETRY,
31+
REGEX,
32+
DEFAULTS,
33+
LIMITS,
34+
PLATFORM,
35+
dashForEmpty: DEFAULTS.DASH_FOR_EMPTY,
36+
};
37+
38+
console.info('✅ Constants global fixture ready');
39+
};

app/test/fixtures/otp.fixture.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { waitForOTP } = require('../pageobjects/utils/airtable.service');
2+
const { TIMEOUT, PAUSE } = require('../pageobjects/utils/constants');
23

34
/**
45
* Global OTP fixture for Mocha tests
@@ -13,7 +14,12 @@ const { waitForOTP } = require('../pageobjects/utils/airtable.service');
1314
* @param {number} pollIntervalMs - Time between polls in milliseconds (default: 5000)
1415
* @returns {Promise<{otp: string, record: object}>} - OTP and record data
1516
*/
16-
async function getOTPFromAirtable(email, afterTime, timeoutMs = 60000, pollIntervalMs = 5000) {
17+
async function getOTPFromAirtable(
18+
email,
19+
afterTime,
20+
timeoutMs = TIMEOUT.OTP_WAIT_MAX,
21+
pollIntervalMs = PAUSE.OTP_POLL_INTERVAL,
22+
) {
1723
console.info(`\n=== OTP Fixture: Getting OTP for ${email} ===`);
1824

1925
try {
@@ -34,9 +40,6 @@ exports.mochaGlobalSetup = async function () {
3440

3541
// Make the OTP function available globally
3642
global.getOTPFromAirtable = getOTPFromAirtable;
37-
global.constants = {
38-
dashForEmpty: '-',
39-
}
4043

4144
// Verify Airtable configuration
4245
const requiredEnvVars = [

app/test/pageobjects/utils/airtable.service.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
const https = require('https');
14+
const { TIMEOUT, PAUSE, REGEX } = require('./constants');
1415

1516
// Configuration - use getter functions to read env vars lazily (after wdio.conf.js loads .env)
1617
const getAirtableConfig = () => {
@@ -122,7 +123,7 @@ async function markAsProcessed(recordId) {
122123
* @returns {string|null} - The 6-digit OTP code or null if not found
123124
*/
124125
function extractOTPFromBody(bodyText) {
125-
const match = bodyText.match(/verification code is:\s*(\d{6})/i);
126+
const match = bodyText.match(REGEX.OTP_FROM_EMAIL_BODY);
126127
return match ? match[1] : null;
127128
}
128129

@@ -185,7 +186,12 @@ async function fetchOTPByEmail(email) {
185186
* @returns {Promise<{otp: string, record: object}>} - OTP and record
186187
* @throws {Error} - If timeout is reached without finding OTP
187188
*/
188-
async function waitForOTP(email, timeoutMs = 60000, pollIntervalMs = 5000, afterTime = new Date()) {
189+
async function waitForOTP(
190+
email,
191+
timeoutMs = TIMEOUT.OTP_WAIT_MAX,
192+
pollIntervalMs = PAUSE.OTP_POLL_INTERVAL,
193+
afterTime = new Date(),
194+
) {
189195
const config = getAirtableConfig();
190196
console.info(`\n=== Waiting for OTP for: ${email} ===`);
191197
console.info(` From Email filter: ${config.fromEmail}`);

app/test/pageobjects/utils/auth.helper.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ const headingPage = require('../base/heading.page');
55
const profilePage = require('../profile.page');
66
const userSettingsPage = require('../user-settings.page');
77
const { isAndroid } = require('./selectors');
8-
const { TIMEOUT, PAUSE } = require('./constants');
8+
const { TIMEOUT, PAUSE, REGEX } = require('./constants');
99
const { restartApp } = require('./app.helper');
1010

1111
const AIRTABLE_EMAIL = process.env.AIRTABLE_EMAIL || 'not-set';
12-
const OTP_TIMEOUT_MS = 120000;
13-
const POLL_INTERVAL_MS = 10000;
12+
const OTP_TIMEOUT_MS = TIMEOUT.OTP_WAIT_MAX;
13+
const POLL_INTERVAL_MS = PAUSE.OTP_POLL_INTERVAL;
1414

1515
/**
1616
* Checks if the user is already logged in by checking for home page elements
@@ -124,7 +124,7 @@ async function loginWithOTP(email = AIRTABLE_EMAIL) {
124124
);
125125

126126
// Verify we got a valid 6-digit OTP
127-
if (!/^\d{6}$/.test(result.otp)) {
127+
if (!REGEX.OTP_6_DIGITS.test(result.otp)) {
128128
throw new Error(`Invalid OTP format: ${result.otp}`);
129129
}
130130
console.info('✓ OTP verification completed successfully');
@@ -155,7 +155,7 @@ async function loginWithOTP(email = AIRTABLE_EMAIL) {
155155
async function waitForAppReady(timeout = TIMEOUT.SCREEN_READY) {
156156
console.info('⏳ Waiting for app to reach ready state...');
157157
const startTime = Date.now();
158-
const pollInterval = 500;
158+
const pollInterval = PAUSE.NAVIGATION;
159159

160160
while (Date.now() - startTime < timeout) {
161161
// Check for home page elements
@@ -274,7 +274,7 @@ async function ensureLoggedOut() {
274274
await userSettingsPage.signOut();
275275

276276
// Wait for welcome page to confirm logout
277-
await welcomePage.welcomeTitle.waitForDisplayed({ timeout: 15000 });
277+
await welcomePage.welcomeTitle.waitForDisplayed({ timeout: TIMEOUT.POST_LOGOUT_REDIRECT });
278278
console.info('✅ User successfully logged out');
279279
} catch (error) {
280280
console.error('❌ Logout failed:', error.message);

app/test/pageobjects/utils/constants.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,27 @@ const TIMEOUT = {
2222

2323
/** Short element wait for fast-loading content */
2424
SHORT_WAIT: 3000,
25+
26+
/** Extended wait for post-logout redirect screen */
27+
POST_LOGOUT_REDIRECT: 15000,
28+
29+
/** Max wait for OTP retrieval polling */
30+
OTP_WAIT_MAX: 60000,
31+
32+
/** Extended OTP e2e flow max wait */
33+
OTP_E2E_MAX: 260000,
34+
35+
/** Additional buffer for OTP e2e test-level timeout */
36+
OTP_E2E_BUFFER: 120000,
37+
38+
/** Max wait for auth flow navigation after OTP submit */
39+
AUTH_FLOW_WAIT: 90000,
40+
41+
/** Standard suite setup timeout used by e2e specs */
42+
TEST_SETUP_LONG: 150000,
43+
44+
/** Default Mocha suite timeout in WDIO config */
45+
MOCHA_SUITE: 600000,
2546
};
2647

2748
// ============ Pause/Delay Constants (milliseconds) ============
@@ -50,6 +71,15 @@ const PAUSE = {
5071
/** Polling interval for status checks */
5172
POLL_INTERVAL: 1000,
5273

74+
/** Polling interval for OTP inbox checks */
75+
OTP_POLL_INTERVAL: 5000,
76+
77+
/** Polling interval used by long-running OTP e2e validation */
78+
OTP_E2E_POLL: 13000,
79+
80+
/** Polling interval while waiting for post-auth home state */
81+
AUTH_FLOW_POLL: 10000,
82+
5383
/** App restart pause after terminate */
5484
APP_TERMINATE: 2000,
5585

@@ -108,10 +138,65 @@ const RETRY = {
108138
MAX_BACK_ATTEMPTS: 5,
109139
};
110140

141+
// ============ Regex / Parsing Constants ============
142+
const REGEX = {
143+
/** Generic leading/trailing whitespace matcher */
144+
TRIM_WHITESPACE: /^\s+|\s+$/g,
145+
146+
/** OTP extraction pattern from email body */
147+
OTP_FROM_EMAIL_BODY: /verification code is:\s*(\d{6})/i,
148+
149+
/** OTP must be exactly six digits */
150+
OTP_6_DIGITS: /^\d{6}$/,
151+
152+
/** Basic email format validation */
153+
EMAIL_BASIC: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
154+
155+
/** Entity ID formats */
156+
ORDER_ID: /^ORD-\d{4}-\d{4}-\d{4}$/,
157+
SUBSCRIPTION_ID: /^SUB-\d{4}-\d{4}-\d{4}$/,
158+
AGREEMENT_ID: /^AGR-\d{4}-\d{4}-\d{4}$/,
159+
AGREEMENT_ID_FLEX: /^AGR-(\d{4}-)+\d{4}$/,
160+
INVOICE_ID: /^INV-(\d{4}-)+\d{4}$/,
161+
CREDIT_MEMO_ID: /^CRD-(\d{4}-)+\d{4}$/,
162+
USER_ID: /^USR-\d{4}-\d{4}$/,
163+
USER_ID_FLEX: /^USR-(\d{4}-)+\d{4}$/,
164+
PROGRAM_ID: /^PRG-\d{4}-\d{4}$/,
165+
BUYER_ID: /^BUY-\d{4}-\d{4}$/,
166+
BUYER_ID_FLEX: /^BUY-(\d{4}-?)+\d{4}$/,
167+
LICENSEE_ID: /^LCE-\d{4}-\d{4}-\d{4}$/,
168+
ENROLLMENT_ID: /^ENR-\d{4}-\d{4}-\d{4}$/,
169+
};
170+
171+
// ============ Default / Sentinel Constants ============
172+
const DEFAULTS = {
173+
/** Placeholder used for empty text values */
174+
DASH_FOR_EMPTY: '-',
175+
};
176+
177+
// ============ Limits Constants ============
178+
const LIMITS = {
179+
/** Generic minimum expected count for list validations */
180+
MIN_EXPECTED_COUNT: 1,
181+
};
182+
183+
// ============ Platform Behavior Constants ============
184+
const PLATFORM = {
185+
/** iOS platform identifier */
186+
IOS: 'ios',
187+
188+
/** Android platform identifier */
189+
ANDROID: 'android',
190+
};
191+
111192
module.exports = {
112193
TIMEOUT,
113194
PAUSE,
114195
SCROLL,
115196
GESTURE,
116197
RETRY,
198+
REGEX,
199+
DEFAULTS,
200+
LIMITS,
201+
PLATFORM,
117202
};

app/test/specs/agreement-details.e2e.js

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
const { expect } = require('@wdio/globals');
22

3-
const agreementsPage = require('../pageobjects/agreements.page');
43
const agreementDetailsPage = require('../pageobjects/agreement-details.page');
4+
const agreementsPage = require('../pageobjects/agreements.page');
55
const morePage = require('../pageobjects/more.page');
66
const { ensureLoggedIn } = require('../pageobjects/utils/auth.helper');
7+
const { TIMEOUT, PAUSE, REGEX } = require('../pageobjects/utils/constants');
78
const navigation = require('../pageobjects/utils/navigation.page');
89
const { apiClient } = require('../utils/api-client');
9-
const subscriptionDetailsPage = require('../pageobjects/subscription-details.page');
1010

1111
// E2E tests for Agreement Details Page, modeled after user-details.e2e.js
1212

@@ -17,12 +17,12 @@ describe('Agreement Details Page', () => {
1717
let apiAgreementData = null;
1818

1919
before(async function () {
20-
this.timeout(150000);
20+
this.timeout(TIMEOUT.TEST_SETUP_LONG);
2121
await ensureLoggedIn();
2222
await navigation.ensureHomePage({ resetFilters: false });
2323
// Navigate to Agreements page via More menu
2424
await agreementsPage.footer.moreTab.click();
25-
await browser.pause(500);
25+
await browser.pause(PAUSE.NAVIGATION);
2626
await morePage.agreementsMenuItem.click();
2727
await agreementsPage.waitForScreenReady();
2828

@@ -37,15 +37,17 @@ describe('Agreement Details Page', () => {
3737
if (apiAvailable && testAgreementId) {
3838
try {
3939
apiAgreementData = await apiClient.getAgreementById(testAgreementId);
40-
console.log(JSON.stringify(apiAgreementData, null, 2));
40+
console.info(JSON.stringify(apiAgreementData, null, 2));
4141
console.info(`📊 Pre-fetched API data for agreement: ${testAgreementId}`);
4242
} catch (error) {
4343
console.warn(`⚠️ Failed to fetch API data: ${error.message}`);
4444
}
4545
}
4646
}
4747

48-
console.info(`📊 Agreement Details test setup: hasAgreements=${hasAgreementsData}, apiAvailable=${apiAvailable}, testAgreementId=${testAgreementId}`);
48+
console.info(
49+
`📊 Agreement Details test setup: hasAgreements=${hasAgreementsData}, apiAvailable=${apiAvailable}, testAgreementId=${testAgreementId}`,
50+
);
4951

5052
// Navigate to agreement details page once at the start
5153
if (hasAgreementsData && testAgreementId) {
@@ -74,7 +76,7 @@ describe('Agreement Details Page', () => {
7476
}
7577
await expect(agreementDetailsPage.agreementIdText).toBeDisplayed();
7678
const agreementId = await agreementDetailsPage.getItemId();
77-
expect(agreementId).toMatch(/^AGR-(\d{4}-)+\d{4}$/);
79+
expect(agreementId).toMatch(REGEX.AGREEMENT_ID_FLEX);
7880
});
7981

8082
it('should display the status field', async function () {
@@ -127,7 +129,10 @@ describe('Agreement Details Page', () => {
127129
this.skip();
128130
return;
129131
}
130-
const billingCurrency = await agreementDetailsPage.getSimpleFieldValue('Billing currency', true);
132+
const billingCurrency = await agreementDetailsPage.getSimpleFieldValue(
133+
'Billing currency',
134+
true,
135+
);
131136
expect(billingCurrency).toBeTruthy();
132137
});
133138
});
@@ -204,7 +209,10 @@ describe('Agreement Details Page', () => {
204209
this.skip();
205210
return;
206211
}
207-
const uiBillingCurrency = await agreementDetailsPage.getSimpleFieldValue('Billing currency', true);
212+
const uiBillingCurrency = await agreementDetailsPage.getSimpleFieldValue(
213+
'Billing currency',
214+
true,
215+
);
208216
const apiBillingCurrency = apiAgreementData.price?.billingCurrency;
209217
console.info(`[Billing Currency] UI: ${uiBillingCurrency} | API: ${apiBillingCurrency}`);
210218
expect(uiBillingCurrency).toBe(apiBillingCurrency);
@@ -221,11 +229,21 @@ describe('Agreement Details Page', () => {
221229
console.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
222230
console.info(`Agreement ID: UI="${uiDetails.agreementId}" | API="${apiAgreementData.id}"`);
223231
console.info(`Status: UI="${uiDetails.status}" | API="${apiAgreementData.status}"`);
224-
console.info(`Vendor: UI="${uiDetails.vendor}" | API="${apiAgreementData.vendor?.name}"`);
225-
console.info(`Product: UI="${uiDetails.product}" | API="${apiAgreementData.product?.name}"`);
226-
console.info(`Client: UI="${uiDetails.client}" | API="${apiAgreementData.client?.name}"`);
227-
console.info(`BaseCurrency: UI="${uiDetails.baseCurrency}" | API="${apiAgreementData.price?.currency}"`);
228-
console.info(`BillingCurr: UI="${uiDetails.billingCurrency}" | API="${apiAgreementData.price?.billingCurrency}"`);
232+
console.info(
233+
`Vendor: UI="${uiDetails.vendor}" | API="${apiAgreementData.vendor?.name}"`,
234+
);
235+
console.info(
236+
`Product: UI="${uiDetails.product}" | API="${apiAgreementData.product?.name}"`,
237+
);
238+
console.info(
239+
`Client: UI="${uiDetails.client}" | API="${apiAgreementData.client?.name}"`,
240+
);
241+
console.info(
242+
`BaseCurrency: UI="${uiDetails.baseCurrency}" | API="${apiAgreementData.price?.currency}"`,
243+
);
244+
console.info(
245+
`BillingCurr: UI="${uiDetails.billingCurrency}" | API="${apiAgreementData.price?.billingCurrency}"`,
246+
);
229247
console.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
230248
expect(uiDetails.agreementId).toBe(apiAgreementData.id);
231249
});

0 commit comments

Comments
 (0)