external-services-automation 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.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # External Services Automation Library
2
+
3
+ A TypeScript library for test automation with Playwright, focused on integrations with external services like Kinde (authentication) and Stripe (payments).
4
+
5
+ ## Setup
6
+
7
+ This library is designed to be used as a Git submodule. Follow these steps to integrate it into your project:
8
+
9
+ ### 1. Add Submodule
10
+
11
+ ```bash
12
+ # Add the submodule
13
+ git submodule add https://bitbucket.org/connectist/external-services-automation.git external-services
14
+
15
+ # Initialize and update
16
+ git submodule update --init --recursive
17
+ ```
18
+
19
+ ### 2. Build Library
20
+
21
+ ```bash
22
+ cd external-services
23
+ npm install
24
+ npm run build
25
+ cd ..
26
+ ```
27
+
28
+ ### 3. Configure Your Test Framework
29
+
30
+ The library can be used with any test framework. Here's an example for Playwright:
31
+
32
+ ```typescript
33
+ // playwright.config.ts
34
+ import { defineConfig } from '@playwright/test';
35
+
36
+ export default defineConfig({
37
+ testDir: './tests',
38
+ use: {
39
+ baseURL: process.env.BASE_URL || 'https://your-app.com',
40
+ },
41
+ });
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### Import Components
47
+
48
+ ```typescript
49
+ import {
50
+ ExternalServicesWorld,
51
+ LoginPage,
52
+ RegisterPage,
53
+ KindeAuthService,
54
+ StripeService,
55
+ GuerrillaMailClient
56
+ } from './external-services/dist/index';
57
+ ```
58
+
59
+ ### Use Services and Page Objects
60
+
61
+ ```typescript
62
+ // Authentication with Kinde
63
+ const authService = new KindeAuthService();
64
+ await authService.login(email, password);
65
+
66
+ // Payment processing with Stripe
67
+ const stripeService = new StripeService();
68
+ await stripeService.upgradeSubscription(planType);
69
+
70
+ // Page interactions
71
+ const loginPage = new LoginPage(page);
72
+ await loginPage.fillCredentials(email, password);
73
+ ```
74
+
75
+ ## Available Components
76
+
77
+ ### Services
78
+
79
+ #### KindeAuthService
80
+ - `login(email: string, password: string)` - Authenticates user with Kinde
81
+ - `register(email: string, firstName: string, lastName: string)` - Creates new account
82
+ - `verifyEmail(code: string)` - Verifies email with confirmation code
83
+
84
+ #### StripeService
85
+ - `upgradeSubscription(planType: string)` - Upgrades subscription to specified plan
86
+ - `updatePaymentMethod(cardDetails: object)` - Updates payment method
87
+ - `getCurrentSubscription()` - Retrieves current subscription details
88
+
89
+ ### Page Objects
90
+
91
+ #### Kinde Pages
92
+ - **LoginPage** - Handles login form interactions
93
+ - **RegisterPage** - Manages account registration
94
+ - **ConfirmCodePage** - Email verification code entry
95
+ - **PasswordSetupPage** - Password creation and setup
96
+
97
+ #### Stripe Pages
98
+ - **CurrentSubscriptionPage** - Displays current subscription details
99
+ - **UpdateSubscriptionPage** - Subscription plan selection
100
+ - **UpdatePaymentMethodPage** - Payment method management
101
+ - **ConfirmUpdatePage** - Confirmation dialogs
102
+ - **LeftPanelPage** - Navigation and sidebar interactions
103
+
104
+ ### Utilities
105
+ - **GuerrillaMailClient** - Temporary email handling for account verification
106
+ - `createTempEmail()` - Creates temporary email address
107
+ - `getVerificationCode()` - Retrieves verification emails
108
+ - `cleanup()` - Cleans up temporary email resources
109
+
110
+ ### World Extension
111
+ - **ExternalServicesWorld** - Shared state management for test scenarios
112
+
113
+ ## Example Implementation
114
+
115
+ ```typescript
116
+ // In your test files
117
+ import { test, expect } from '@playwright/test';
118
+ import { KindeAuthService, StripeService, GuerrillaMailClient } from './external-services/dist/index';
119
+
120
+ test.describe('External Services Integration', () => {
121
+ let authService: KindeAuthService;
122
+ let stripeService: StripeService;
123
+ let tempEmail: GuerrillaMailClient;
124
+
125
+ test.beforeEach(async () => {
126
+ authService = new KindeAuthService();
127
+ stripeService = new StripeService();
128
+ });
129
+
130
+ test('should create account and upgrade subscription', async () => {
131
+ // Create temporary email for account verification
132
+ tempEmail = new GuerrillaMailClient();
133
+ const email = await tempEmail.createTempEmail();
134
+
135
+ // Register new account
136
+ await authService.register(email, 'John', 'Doe');
137
+
138
+ // Verify email
139
+ const verificationCode = await tempEmail.getVerificationCode();
140
+ await authService.verifyEmail(verificationCode);
141
+
142
+ // Upgrade subscription
143
+ await stripeService.upgradeSubscription('premium');
144
+
145
+ // Verify subscription status
146
+ const subscription = await stripeService.getCurrentSubscription();
147
+ expect(subscription.status).toBe('active');
148
+
149
+ // Cleanup
150
+ await tempEmail.cleanup();
151
+ });
152
+
153
+ test('should handle login with existing account', async () => {
154
+ const result = await authService.login('user@example.com', 'password123');
155
+ expect(result.success).toBe(true);
156
+ });
157
+ });
158
+ ```
159
+
160
+ ## Update Submodule
161
+
162
+ ```bash
163
+ # Update to latest
164
+ git submodule update --remote external-services
165
+
166
+ # Build updated library
167
+ cd external-services && npm run build && cd ..
168
+
169
+ # Commit update
170
+ git add external-services
171
+ git commit -m "Update external-services submodule"
172
+ ```
173
+
174
+ ## CI/CD
175
+
176
+ ### Bitbucket Pipelines
177
+
178
+ ```yaml
179
+ script:
180
+ - git submodule update --init --recursive
181
+ - npm install
182
+ - cd external-services && npm install && npm run build && cd ..
183
+ - npm test
184
+ ```
185
+
186
+ ## Dependencies
187
+
188
+ The library requires the following peer dependencies:
189
+ - `@playwright/test` ^1.40.0
190
+ - `axios-cookiejar-support` ^6.0.2
191
+ - `dotenv` ^16.4.5
192
+ - `tough-cookie` ^5.1.2
193
+
194
+ ## Troubleshooting
195
+
196
+ ### Submodule Not Found
197
+ ```bash
198
+ git submodule update --init --recursive
199
+ ```
200
+
201
+ ### Import Errors
202
+ ```bash
203
+ cd external-services && npm run build && cd ..
204
+ ```
205
+
206
+ ### Missing Dependencies
207
+ ```bash
208
+ npm install @playwright/test axios-cookiejar-support dotenv tough-cookie
209
+ ```
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ npm install
215
+ npm run build
216
+ ```
@@ -0,0 +1,13 @@
1
+ export { KindeAuthService } from './services/KindeAuthService';
2
+ export { StripeService } from './services/StripeService';
3
+ export { LoginPage } from './pages/kinde/LoginPage';
4
+ export { RegisterPage } from './pages/kinde/RegisterPage';
5
+ export { PasswordSetupPage } from './pages/kinde/PasswordSetupPage';
6
+ export { ConfirmCodePage } from './pages/kinde/ConfirmCodePage';
7
+ export { CurrentSubscriptionPage } from './pages/stripe/CurrentSubscriptionPage';
8
+ export { UpdateSubscriptionPage, PlanType } from './pages/stripe/UpdateSubscriptionPage';
9
+ export { UpdatePaymentMethodPage } from './pages/stripe/UpdatePaymentMethodPage';
10
+ export { ConfirmUpdatePage } from './pages/stripe/ConfirmUpdatePage';
11
+ export { LeftPanelPage } from './pages/stripe/LeftPanelPage';
12
+ export { GuerrillaMailClient } from './utils/temp-email-utils';
13
+ export type { Page, BrowserContext, Locator } from 'playwright/test';
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GuerrillaMailClient = exports.LeftPanelPage = exports.ConfirmUpdatePage = exports.UpdatePaymentMethodPage = exports.UpdateSubscriptionPage = exports.CurrentSubscriptionPage = exports.ConfirmCodePage = exports.PasswordSetupPage = exports.RegisterPage = exports.LoginPage = exports.StripeService = exports.KindeAuthService = void 0;
4
+ // Export services
5
+ var KindeAuthService_1 = require("./services/KindeAuthService");
6
+ Object.defineProperty(exports, "KindeAuthService", { enumerable: true, get: function () { return KindeAuthService_1.KindeAuthService; } });
7
+ var StripeService_1 = require("./services/StripeService");
8
+ Object.defineProperty(exports, "StripeService", { enumerable: true, get: function () { return StripeService_1.StripeService; } });
9
+ // Export all Kinde pages
10
+ var LoginPage_1 = require("./pages/kinde/LoginPage");
11
+ Object.defineProperty(exports, "LoginPage", { enumerable: true, get: function () { return LoginPage_1.LoginPage; } });
12
+ var RegisterPage_1 = require("./pages/kinde/RegisterPage");
13
+ Object.defineProperty(exports, "RegisterPage", { enumerable: true, get: function () { return RegisterPage_1.RegisterPage; } });
14
+ var PasswordSetupPage_1 = require("./pages/kinde/PasswordSetupPage");
15
+ Object.defineProperty(exports, "PasswordSetupPage", { enumerable: true, get: function () { return PasswordSetupPage_1.PasswordSetupPage; } });
16
+ var ConfirmCodePage_1 = require("./pages/kinde/ConfirmCodePage");
17
+ Object.defineProperty(exports, "ConfirmCodePage", { enumerable: true, get: function () { return ConfirmCodePage_1.ConfirmCodePage; } });
18
+ // Export all Stripe pages
19
+ var CurrentSubscriptionPage_1 = require("./pages/stripe/CurrentSubscriptionPage");
20
+ Object.defineProperty(exports, "CurrentSubscriptionPage", { enumerable: true, get: function () { return CurrentSubscriptionPage_1.CurrentSubscriptionPage; } });
21
+ var UpdateSubscriptionPage_1 = require("./pages/stripe/UpdateSubscriptionPage");
22
+ Object.defineProperty(exports, "UpdateSubscriptionPage", { enumerable: true, get: function () { return UpdateSubscriptionPage_1.UpdateSubscriptionPage; } });
23
+ var UpdatePaymentMethodPage_1 = require("./pages/stripe/UpdatePaymentMethodPage");
24
+ Object.defineProperty(exports, "UpdatePaymentMethodPage", { enumerable: true, get: function () { return UpdatePaymentMethodPage_1.UpdatePaymentMethodPage; } });
25
+ var ConfirmUpdatePage_1 = require("./pages/stripe/ConfirmUpdatePage");
26
+ Object.defineProperty(exports, "ConfirmUpdatePage", { enumerable: true, get: function () { return ConfirmUpdatePage_1.ConfirmUpdatePage; } });
27
+ var LeftPanelPage_1 = require("./pages/stripe/LeftPanelPage");
28
+ Object.defineProperty(exports, "LeftPanelPage", { enumerable: true, get: function () { return LeftPanelPage_1.LeftPanelPage; } });
29
+ // Export utilities
30
+ var temp_email_utils_1 = require("./utils/temp-email-utils");
31
+ Object.defineProperty(exports, "GuerrillaMailClient", { enumerable: true, get: function () { return temp_email_utils_1.GuerrillaMailClient; } });
@@ -0,0 +1,12 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export declare class ConfirmCodePage {
3
+ readonly page: Page;
4
+ readonly pageTitle: Locator;
5
+ readonly codeInput: Locator;
6
+ readonly continueButton: Locator;
7
+ constructor(page: Page);
8
+ enterConfirmationCode(code: string): Promise<void>;
9
+ clickContinue(): Promise<void>;
10
+ submitConfirmationCode(code: string): Promise<void>;
11
+ isOnConfirmCodePage(): Promise<void>;
12
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfirmCodePage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class ConfirmCodePage {
6
+ constructor(page) {
7
+ this.page = page;
8
+ this.pageTitle = page.locator('h1:has-text("Almost there — check your inbox")');
9
+ this.codeInput = page.locator('input[name="p_confirmation_code"]');
10
+ this.continueButton = page.locator('button[type="submit"]:has-text("Continue")');
11
+ }
12
+ async enterConfirmationCode(code) {
13
+ await this.codeInput.fill(code);
14
+ }
15
+ async clickContinue() {
16
+ await this.continueButton.click();
17
+ }
18
+ async submitConfirmationCode(code) {
19
+ await this.enterConfirmationCode(code);
20
+ await this.clickContinue();
21
+ }
22
+ async isOnConfirmCodePage() {
23
+ await (0, test_1.expect)(this.pageTitle).toBeVisible();
24
+ await (0, test_1.expect)(this.codeInput).toBeVisible();
25
+ await (0, test_1.expect)(this.continueButton).toBeVisible();
26
+ }
27
+ }
28
+ exports.ConfirmCodePage = ConfirmCodePage;
@@ -0,0 +1,16 @@
1
+ import { Locator, Page } from '@playwright/test';
2
+ export declare class LoginPage {
3
+ readonly page: Page;
4
+ readonly emailInput: Locator;
5
+ readonly nextButton: Locator;
6
+ readonly passwordInput: Locator;
7
+ readonly email_not_exists: Locator;
8
+ readonly createAccountLink: Locator;
9
+ constructor(page: Page);
10
+ navigate(): Promise<void>;
11
+ enterEmail(email: string): Promise<void>;
12
+ enterPassword(password: string): Promise<void>;
13
+ getEmailNotExistsMessage(): Promise<string | null>;
14
+ login(email: string, password?: string): Promise<void>;
15
+ goToRegisterPage(): Promise<void>;
16
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoginPage = void 0;
4
+ class LoginPage {
5
+ constructor(page) {
6
+ this.page = page;
7
+ this.emailInput = page.locator('#sign_up_sign_in_credentials_p_email');
8
+ this.nextButton = page.locator('.kinde-button.kinde-button-variant-primary');
9
+ this.passwordInput = page.locator('#verify_password_p_password');
10
+ this.email_not_exists = page.locator('#sign_up_sign_in_credentials_p_email_error_msg');
11
+ this.createAccountLink = page.locator('.kinde-text-link[href*="register"]');
12
+ }
13
+ async navigate() {
14
+ await this.page.goto('/login');
15
+ }
16
+ async enterEmail(email) {
17
+ await this.emailInput.fill(email);
18
+ await this.nextButton.click();
19
+ await this.page.waitForTimeout(2000);
20
+ }
21
+ async enterPassword(password) {
22
+ await this.passwordInput.fill(password);
23
+ await this.nextButton.click();
24
+ await this.page.waitForTimeout(3000);
25
+ }
26
+ async getEmailNotExistsMessage() {
27
+ return await this.email_not_exists.textContent();
28
+ }
29
+ async login(email, password) {
30
+ if (!email) {
31
+ throw new Error(`${email} is not set`);
32
+ }
33
+ await this.enterEmail(email);
34
+ if (password) {
35
+ await this.enterPassword(password);
36
+ }
37
+ }
38
+ async goToRegisterPage() {
39
+ await this.createAccountLink.click();
40
+ }
41
+ }
42
+ exports.LoginPage = LoginPage;
@@ -0,0 +1,14 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export declare class PasswordSetupPage {
3
+ readonly page: Page;
4
+ readonly pageTitle: Locator;
5
+ readonly passwordInput: Locator;
6
+ readonly confirmPasswordInput: Locator;
7
+ readonly continueButton: Locator;
8
+ constructor(page: Page);
9
+ enterPassword(password: string): Promise<void>;
10
+ enterConfirmPassword(password: string): Promise<void>;
11
+ clickContinue(): Promise<void>;
12
+ submitPasswordForm(password: string): Promise<void>;
13
+ isOnPasswordSetupPage(): Promise<void>;
14
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PasswordSetupPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class PasswordSetupPage {
6
+ constructor(page) {
7
+ this.page = page;
8
+ this.pageTitle = page.locator('h1:has-text("Password setup")');
9
+ this.passwordInput = page.locator('input[name="p_first_password"]');
10
+ this.confirmPasswordInput = page.locator('input[name="p_second_password"]');
11
+ this.continueButton = page.locator('button[type="submit"]:has-text("Continue")');
12
+ }
13
+ async enterPassword(password) {
14
+ await this.passwordInput.fill(password);
15
+ }
16
+ async enterConfirmPassword(password) {
17
+ await this.confirmPasswordInput.fill(password);
18
+ }
19
+ async clickContinue() {
20
+ await this.continueButton.click();
21
+ }
22
+ async submitPasswordForm(password) {
23
+ await this.enterPassword(password);
24
+ await this.enterConfirmPassword(password);
25
+ await this.clickContinue();
26
+ }
27
+ async isOnPasswordSetupPage() {
28
+ await (0, test_1.expect)(this.pageTitle).toBeVisible();
29
+ await (0, test_1.expect)(this.passwordInput).toBeVisible();
30
+ await (0, test_1.expect)(this.confirmPasswordInput).toBeVisible();
31
+ await (0, test_1.expect)(this.continueButton).toBeVisible();
32
+ }
33
+ }
34
+ exports.PasswordSetupPage = PasswordSetupPage;
@@ -0,0 +1,20 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export declare class RegisterPage {
3
+ readonly page: Page;
4
+ readonly firstNameInput: Locator;
5
+ readonly lastNameInput: Locator;
6
+ readonly emailInput: Locator;
7
+ readonly marketingOptInCheckbox: Locator;
8
+ readonly acceptPoliciesCheckbox: Locator;
9
+ readonly createAccountButton: Locator;
10
+ constructor(page: Page);
11
+ navigate(): Promise<void>;
12
+ enterFirstName(firstName: string): Promise<void>;
13
+ enterLastName(lastName: string): Promise<void>;
14
+ enterEmail(email: string): Promise<void>;
15
+ checkMarketingOptIn(): Promise<void>;
16
+ checkAcceptPolicies(): Promise<void>;
17
+ clickCreateAccount(): Promise<void>;
18
+ fillAndSubmitCreateAccountForm(firstName: string, lastName: string, email: string, acceptMarketing?: boolean): Promise<void>;
19
+ isOnRegisterPage(): Promise<void>;
20
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RegisterPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class RegisterPage {
6
+ constructor(page) {
7
+ this.page = page;
8
+ this.firstNameInput = page.locator('input[name="p_first_name"]');
9
+ this.lastNameInput = page.locator('input[name="p_last_name"]');
10
+ this.emailInput = page.locator('input[name="p_email"]');
11
+ this.marketingOptInCheckbox = page.locator('input[name="p_kp_usr_is_marketing_opt_in"]');
12
+ this.acceptPoliciesCheckbox = page.locator('input[name="p_has_clickwrap_accepted"]');
13
+ this.createAccountButton = page.locator('button[type="submit"]:has-text("Create your account")');
14
+ }
15
+ async navigate() {
16
+ await this.page.goto('/auth/register');
17
+ await this.isOnRegisterPage();
18
+ }
19
+ async enterFirstName(firstName) {
20
+ await this.firstNameInput.fill(firstName);
21
+ }
22
+ async enterLastName(lastName) {
23
+ await this.lastNameInput.fill(lastName);
24
+ }
25
+ async enterEmail(email) {
26
+ await this.emailInput.fill(email);
27
+ }
28
+ async checkMarketingOptIn() {
29
+ await this.marketingOptInCheckbox.check();
30
+ }
31
+ async checkAcceptPolicies() {
32
+ await this.acceptPoliciesCheckbox.check();
33
+ }
34
+ async clickCreateAccount() {
35
+ await this.createAccountButton.click();
36
+ }
37
+ async fillAndSubmitCreateAccountForm(firstName, lastName, email, acceptMarketing = false) {
38
+ await this.enterFirstName(firstName);
39
+ await this.enterLastName(lastName);
40
+ await this.enterEmail(email);
41
+ if (acceptMarketing) {
42
+ await this.checkMarketingOptIn();
43
+ }
44
+ await this.checkAcceptPolicies();
45
+ await this.clickCreateAccount();
46
+ }
47
+ async isOnRegisterPage() {
48
+ await (0, test_1.expect)(this.firstNameInput).toBeVisible();
49
+ await (0, test_1.expect)(this.lastNameInput).toBeVisible();
50
+ await (0, test_1.expect)(this.emailInput).toBeVisible();
51
+ await (0, test_1.expect)(this.marketingOptInCheckbox).toBeVisible();
52
+ await (0, test_1.expect)(this.acceptPoliciesCheckbox).toBeVisible();
53
+ await (0, test_1.expect)(this.createAccountButton).toBeVisible();
54
+ }
55
+ }
56
+ exports.RegisterPage = RegisterPage;
@@ -0,0 +1,9 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export declare class ConfirmUpdatePage {
3
+ readonly page: Page;
4
+ readonly pageContainer: Locator;
5
+ readonly confirmButton: Locator;
6
+ constructor(page: Page);
7
+ clickConfirm(): Promise<void>;
8
+ isOnConfirmUpdatePage(): Promise<void>;
9
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfirmUpdatePage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class ConfirmUpdatePage {
6
+ constructor(page) {
7
+ this.page = page;
8
+ this.pageContainer = page
9
+ .locator('[data-testid="page-container-main"], .Box-root, [data-testid="confirm-container"]')
10
+ .first();
11
+ this.confirmButton = page.locator('button:has-text("Confirm"), ' +
12
+ '[data-testid="confirm-button"], ' +
13
+ '[data-test="confirm-button"], ' +
14
+ 'a:has-text("Confirm"), ' +
15
+ 'button[type="submit"]:has-text("Confirm"), ' +
16
+ '.confirm-button, ' +
17
+ '#confirm-button');
18
+ }
19
+ async clickConfirm() {
20
+ await this.confirmButton.click();
21
+ }
22
+ async isOnConfirmUpdatePage() {
23
+ await (0, test_1.expect)(this.pageContainer).toBeVisible();
24
+ await (0, test_1.expect)(this.confirmButton).toBeVisible();
25
+ }
26
+ }
27
+ exports.ConfirmUpdatePage = ConfirmUpdatePage;
@@ -0,0 +1,11 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export declare class CurrentSubscriptionPage {
3
+ readonly page: Page;
4
+ readonly pageContainer: Locator;
5
+ readonly planName: Locator;
6
+ readonly updateSubscriptionButton: Locator;
7
+ readonly currentSubscriptionPageTitle: Locator;
8
+ constructor(page: Page);
9
+ navigateToUpdateSubscription(): Promise<void>;
10
+ isOnCurrentSubscriptionPage(): Promise<void>;
11
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CurrentSubscriptionPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class CurrentSubscriptionPage {
6
+ constructor(page) {
7
+ this.page = page;
8
+ this.pageContainer = page.locator('[data-testid="page-container-main"]');
9
+ this.planName = page.locator('[class*="Text-fontSize--20"]:has-text("plan")').first();
10
+ this.updateSubscriptionButton = page.locator('[data-test="update-subscription"]');
11
+ this.currentSubscriptionPageTitle = page.locator('//span[text()= "Current subscription"]');
12
+ }
13
+ async navigateToUpdateSubscription() {
14
+ await this.updateSubscriptionButton.click();
15
+ }
16
+ async isOnCurrentSubscriptionPage() {
17
+ await (0, test_1.expect)(this.currentSubscriptionPageTitle).toBeVisible();
18
+ }
19
+ }
20
+ exports.CurrentSubscriptionPage = CurrentSubscriptionPage;
@@ -0,0 +1,11 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export declare class LeftPanelPage {
3
+ readonly page: Page;
4
+ readonly panelContainer: Locator;
5
+ readonly emailAwesomeLogo: Locator;
6
+ readonly sandboxIndicator: Locator;
7
+ readonly businessTitle: Locator;
8
+ readonly returnToBusinessButton: Locator;
9
+ constructor(page: Page);
10
+ navigateBackToEmailAwesome(): Promise<void>;
11
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LeftPanelPage = void 0;
4
+ class LeftPanelPage {
5
+ constructor(page) {
6
+ this.page = page;
7
+ this.panelContainer = page.locator('div[style*="background-color: rgb(232, 245, 233)"]');
8
+ this.emailAwesomeLogo = page.locator('img[data-test="logo-img"]');
9
+ this.sandboxIndicator = page.locator('span:has-text("Sandbox")');
10
+ this.businessTitle = page.locator('span:has-text("Email Awesome")').first();
11
+ this.returnToBusinessButton = page.locator('[data-testid="return-to-business-link"]');
12
+ }
13
+ async navigateBackToEmailAwesome() {
14
+ await this.returnToBusinessButton.click();
15
+ }
16
+ }
17
+ exports.LeftPanelPage = LeftPanelPage;
@@ -0,0 +1,11 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export declare class UpdatePaymentMethodPage {
3
+ readonly page: Page;
4
+ readonly pageContainer: Locator;
5
+ readonly stripeIframe: Locator;
6
+ readonly continueButton: Locator;
7
+ constructor(page: Page);
8
+ clickContinue(): Promise<void>;
9
+ isOnUpdatePaymentMethodPage(): Promise<void>;
10
+ fillCardInformation(): Promise<void>;
11
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UpdatePaymentMethodPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class UpdatePaymentMethodPage {
6
+ constructor(page) {
7
+ this.page = page;
8
+ this.pageContainer = page.locator('[data-testid="page-container-main"]').first();
9
+ this.stripeIframe = page.locator('iframe[src*="stripe.com"]').first();
10
+ this.continueButton = page.locator('button[data-testid="confirm"]');
11
+ }
12
+ async clickContinue() {
13
+ await this.continueButton.click();
14
+ }
15
+ async isOnUpdatePaymentMethodPage() {
16
+ await (0, test_1.expect)(this.pageContainer).toBeVisible();
17
+ await (0, test_1.expect)(this.stripeIframe).toBeVisible();
18
+ await (0, test_1.expect)(this.continueButton).toBeVisible();
19
+ }
20
+ async fillCardInformation() {
21
+ const cardNumber = '4242424242424242';
22
+ const expirationDate = '12/34';
23
+ const securityCode = '567';
24
+ try {
25
+ await this.stripeIframe.waitFor({ state: 'visible' });
26
+ // Get the iframe element and switch to it
27
+ const iframeElement = await this.stripeIframe.elementHandle();
28
+ if (!iframeElement) {
29
+ throw new Error('Stripe iframe not found');
30
+ }
31
+ // Switch to the iframe context
32
+ const iframePage = await iframeElement.contentFrame();
33
+ if (!iframePage) {
34
+ throw new Error('Could not access iframe content');
35
+ }
36
+ const cardNumberField = iframePage.locator('input[name="number"]');
37
+ const expirationDateField = iframePage.locator('input[name="expiry"]');
38
+ const securityCodeField = iframePage.locator('input[name="cvc"]');
39
+ await cardNumberField.fill(cardNumber);
40
+ await expirationDateField.fill(expirationDate);
41
+ await securityCodeField.fill(securityCode);
42
+ await this.page.bringToFront();
43
+ }
44
+ catch (error) {
45
+ console.error('Error filling card information:', error);
46
+ if (!cardNumber || !expirationDate || !securityCode) {
47
+ throw new Error(`Cannot use keyboard fallback - missing data: cardNumber=${cardNumber}, expirationDate=${expirationDate}, securityCode=${securityCode}`);
48
+ }
49
+ throw error;
50
+ }
51
+ }
52
+ }
53
+ exports.UpdatePaymentMethodPage = UpdatePaymentMethodPage;
@@ -0,0 +1,14 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ export type PlanType = 'lite' | 'basic' | 'growth' | 'pro' | 'business' | 'enterprise';
3
+ export declare class UpdateSubscriptionPage {
4
+ readonly page: Page;
5
+ readonly pageContainer: Locator;
6
+ readonly planCardsContainer: Locator;
7
+ readonly continueButton: Locator;
8
+ readonly backButton: Locator;
9
+ readonly selectPlanButton: (planType: PlanType) => Locator;
10
+ constructor(page: Page);
11
+ selectPlan(planType: PlanType): Promise<void>;
12
+ clickContinue(): Promise<void>;
13
+ isOnUpdateSubscriptionPage(): Promise<void>;
14
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UpdateSubscriptionPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class UpdateSubscriptionPage {
6
+ constructor(page) {
7
+ this.page = page;
8
+ this.pageContainer = page.locator('[data-testid="page-container-main"], .Box-root').first();
9
+ this.planCardsContainer = page.locator('[data-testid="pricing-table"]');
10
+ this.continueButton = page.locator('button:has-text("Continue"), [data-testid="continue-button"], a:has-text("Continue"), a[role="button"]:has-text("Continue")');
11
+ this.backButton = page.locator('button:has-text("Back"), [data-testid="back-button"], a:has-text("Back"), a[role="button"]:has-text("Back")');
12
+ this.selectPlanButton = (planType) => this.page.locator(`//div[contains(text(),'${planType.charAt(0).toUpperCase() + planType.slice(1)}')]/ancestor::div[@data-testid='pricing-table-card']//span[text()='Select']`);
13
+ }
14
+ async selectPlan(planType) {
15
+ await this.selectPlanButton(planType).click();
16
+ }
17
+ async clickContinue() {
18
+ await this.continueButton.click();
19
+ }
20
+ async isOnUpdateSubscriptionPage() {
21
+ await (0, test_1.expect)(this.pageContainer).toBeVisible();
22
+ await (0, test_1.expect)(this.planCardsContainer).toBeVisible();
23
+ }
24
+ }
25
+ exports.UpdateSubscriptionPage = UpdateSubscriptionPage;
@@ -0,0 +1,13 @@
1
+ import { Page } from 'playwright/test';
2
+ export declare class KindeAuthService {
3
+ private tempEmail?;
4
+ private verificationCode?;
5
+ private emailClient?;
6
+ private loginPage;
7
+ private registerPage;
8
+ private confirmCodePage;
9
+ private passwordSetupPage;
10
+ constructor(page: Page);
11
+ createAccountWithEmailVerification(): Promise<void>;
12
+ deleteTemporaryEmail(): Promise<void>;
13
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KindeAuthService = void 0;
4
+ const ConfirmCodePage_1 = require("../pages/kinde/ConfirmCodePage");
5
+ const LoginPage_1 = require("../pages/kinde/LoginPage");
6
+ const PasswordSetupPage_1 = require("../pages/kinde/PasswordSetupPage");
7
+ const RegisterPage_1 = require("../pages/kinde/RegisterPage");
8
+ const temp_email_utils_1 = require("../utils/temp-email-utils");
9
+ class KindeAuthService {
10
+ constructor(page) {
11
+ this.loginPage = new LoginPage_1.LoginPage(page);
12
+ this.registerPage = new RegisterPage_1.RegisterPage(page);
13
+ this.confirmCodePage = new ConfirmCodePage_1.ConfirmCodePage(page);
14
+ this.passwordSetupPage = new PasswordSetupPage_1.PasswordSetupPage(page);
15
+ }
16
+ async createAccountWithEmailVerification() {
17
+ await this.loginPage.navigate();
18
+ await this.loginPage.goToRegisterPage();
19
+ await this.registerPage.isOnRegisterPage();
20
+ this.emailClient = temp_email_utils_1.GuerrillaMailClient.createIsolatedClient();
21
+ this.tempEmail = await this.emailClient.createMail();
22
+ const firstName = 'Test';
23
+ const lastName = 'User';
24
+ await this.registerPage.fillAndSubmitCreateAccountForm(firstName, lastName, this.tempEmail, false // Don't accept marketing emails
25
+ );
26
+ await this.confirmCodePage.isOnConfirmCodePage();
27
+ const emailData = await this.emailClient.readMailBySubject(/Email verification code/i, 120000, // 2 minutes timeout
28
+ 5000 // Check every 5 seconds
29
+ );
30
+ const codeMatch = emailData.mail_body?.match(/\b\d{6}\b/);
31
+ if (codeMatch) {
32
+ this.verificationCode = codeMatch[0];
33
+ await this.confirmCodePage.submitConfirmationCode(this.verificationCode);
34
+ }
35
+ else {
36
+ throw new Error('Could not extract verification code from email');
37
+ }
38
+ await this.passwordSetupPage.isOnPasswordSetupPage();
39
+ const password = 'TestPassword123!';
40
+ await this.passwordSetupPage.submitPasswordForm(password);
41
+ }
42
+ async deleteTemporaryEmail() {
43
+ if (this.tempEmail) {
44
+ try {
45
+ this.tempEmail = undefined;
46
+ this.verificationCode = undefined;
47
+ this.emailClient = undefined;
48
+ }
49
+ catch (error) {
50
+ console.warn("Could not clean up temporary email:", error);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ exports.KindeAuthService = KindeAuthService;
@@ -0,0 +1,12 @@
1
+ import { Page } from 'playwright/test';
2
+ export declare class StripeService {
3
+ private page;
4
+ private stripeCurrentSubscriptionPage;
5
+ private stripeUpdateSubscriptionPage;
6
+ private stripeConfirmUpdatePage;
7
+ private stripeUpdatePaymentMethodPage;
8
+ private stripeLeftPanelPage;
9
+ constructor(page: Page);
10
+ upgradePlan(planType: string): Promise<void>;
11
+ completePayment(): Promise<void>;
12
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StripeService = void 0;
4
+ const test_1 = require("playwright/test");
5
+ const ConfirmUpdatePage_1 = require("../pages/stripe/ConfirmUpdatePage");
6
+ const CurrentSubscriptionPage_1 = require("../pages/stripe/CurrentSubscriptionPage");
7
+ const LeftPanelPage_1 = require("../pages/stripe/LeftPanelPage");
8
+ const UpdatePaymentMethodPage_1 = require("../pages/stripe/UpdatePaymentMethodPage");
9
+ const UpdateSubscriptionPage_1 = require("../pages/stripe/UpdateSubscriptionPage");
10
+ class StripeService {
11
+ constructor(page) {
12
+ this.page = page;
13
+ this.stripeCurrentSubscriptionPage = new CurrentSubscriptionPage_1.CurrentSubscriptionPage(page);
14
+ this.stripeUpdateSubscriptionPage = new UpdateSubscriptionPage_1.UpdateSubscriptionPage(page);
15
+ this.stripeConfirmUpdatePage = new ConfirmUpdatePage_1.ConfirmUpdatePage(page);
16
+ this.stripeUpdatePaymentMethodPage = new UpdatePaymentMethodPage_1.UpdatePaymentMethodPage(page);
17
+ this.stripeLeftPanelPage = new LeftPanelPage_1.LeftPanelPage(page);
18
+ }
19
+ async upgradePlan(planType) {
20
+ await (0, test_1.expect)(this.page).toHaveURL(/billing\.stripe\.com/, { timeout: 20000 });
21
+ await this.page.waitForLoadState('domcontentloaded');
22
+ await this.stripeCurrentSubscriptionPage.navigateToUpdateSubscription();
23
+ await this.stripeUpdateSubscriptionPage.isOnUpdateSubscriptionPage();
24
+ const planTypeFormatted = planType.toLowerCase();
25
+ await this.stripeUpdateSubscriptionPage.selectPlan(planTypeFormatted);
26
+ await this.stripeUpdateSubscriptionPage.clickContinue();
27
+ await this.stripeConfirmUpdatePage.isOnConfirmUpdatePage();
28
+ await this.stripeConfirmUpdatePage.clickConfirm();
29
+ }
30
+ async completePayment() {
31
+ await (0, test_1.expect)(this.page).toHaveURL(/update-payment-method/, { timeout: 10000 });
32
+ await this.stripeUpdatePaymentMethodPage.isOnUpdatePaymentMethodPage();
33
+ await this.stripeUpdatePaymentMethodPage.fillCardInformation();
34
+ await this.stripeUpdatePaymentMethodPage.clickContinue();
35
+ await this.stripeCurrentSubscriptionPage.isOnCurrentSubscriptionPage();
36
+ await this.stripeLeftPanelPage.navigateBackToEmailAwesome();
37
+ await (0, test_1.expect)(this.page).toHaveURL(/emailawesome\.com/, { timeout: 10000 });
38
+ await (0, test_1.expect)(this.page).not.toHaveURL(/stripe\.com/);
39
+ }
40
+ }
41
+ exports.StripeService = StripeService;
@@ -0,0 +1,49 @@
1
+ /** Structure returned by Guerrilla Mail for a single message */
2
+ export interface GuerrillaEmail {
3
+ mail_id: string;
4
+ mail_subject: string;
5
+ mail_from: string;
6
+ mail_excerpt?: string;
7
+ mail_body?: string;
8
+ mail_timestamp: number;
9
+ }
10
+ declare class GuerrillaMailClient {
11
+ private readonly API_URL;
12
+ private readonly ip;
13
+ private readonly agent;
14
+ private seq;
15
+ private emailAddress;
16
+ private client;
17
+ constructor();
18
+ /**
19
+ * Function 1: Crea un email temporal. Si se pasa un alias se fuerza ese usuario.
20
+ * @param alias Nombre de usuario deseado (sin dominio).
21
+ * @returns Dirección de email completa.
22
+ */
23
+ createMail(alias?: string): Promise<string>;
24
+ /**
25
+ * Function 2: Lee la bandeja y devuelve el email MÁS RECIENTE cuyo subject matchee con subjectRegex.
26
+ * Hace polling hasta timeout.
27
+ * @param subjectRegex Expresión regular a matchear con el asunto.
28
+ * @param timeoutMs Tiempo máximo a esperar (ms).
29
+ * @param pollMs Intervalo entre consultas (ms).
30
+ */
31
+ readMailBySubject(subjectRegex: RegExp, timeoutMs?: number, pollMs?: number): Promise<GuerrillaEmail>;
32
+ /**
33
+ * Function 3: Elimina un correo específico del servidor.
34
+ * @param mailId ID retornado por la API (mail_id).
35
+ */
36
+ deleteMail(mailId: string): Promise<void>;
37
+ /** Helper interno para descargar cuerpo completo */
38
+ private fetchEmail;
39
+ /**
40
+ * Function 4: Crea una nueva instancia de email client para aislamiento entre escenarios
41
+ */
42
+ static createIsolatedClient(): GuerrillaMailClient;
43
+ /**
44
+ * Function 5: Elimina todos los emails del buzón actual
45
+ */
46
+ clearInbox(): Promise<void>;
47
+ }
48
+ export declare const email: GuerrillaMailClient;
49
+ export { GuerrillaMailClient };
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GuerrillaMailClient = exports.email = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const axios_cookiejar_support_1 = require("axios-cookiejar-support");
9
+ const tough_cookie_1 = require("tough-cookie");
10
+ class GuerrillaMailClient {
11
+ constructor() {
12
+ this.API_URL = 'https://api.guerrillamail.com/ajax.php';
13
+ this.ip = '127.0.0.1';
14
+ this.agent = encodeURIComponent('qa-automation-utils/1.0');
15
+ this.seq = 0;
16
+ this.emailAddress = '';
17
+ const jar = new tough_cookie_1.CookieJar();
18
+ this.client = (0, axios_cookiejar_support_1.wrapper)(axios_1.default.create({
19
+ jar,
20
+ withCredentials: true,
21
+ headers: {
22
+ // Recommended to identify politely
23
+ 'User-Agent': 'qa-automation-utils/1.0',
24
+ },
25
+ }));
26
+ }
27
+ /**
28
+ * Function 1: Crea un email temporal. Si se pasa un alias se fuerza ese usuario.
29
+ * @param alias Nombre de usuario deseado (sin dominio).
30
+ * @returns Dirección de email completa.
31
+ */
32
+ async createMail(alias) {
33
+ if (alias) {
34
+ const { data } = await this.client.get(this.API_URL, {
35
+ params: {
36
+ f: 'set_email_user',
37
+ email_user: alias,
38
+ ip: this.ip,
39
+ agent: this.agent,
40
+ },
41
+ });
42
+ this.emailAddress = data.email_addr;
43
+ }
44
+ else {
45
+ const { data } = await this.client.get(this.API_URL, {
46
+ params: {
47
+ f: 'get_email_address',
48
+ ip: this.ip,
49
+ agent: this.agent,
50
+ },
51
+ });
52
+ this.emailAddress = data.email_addr;
53
+ }
54
+ return this.emailAddress;
55
+ }
56
+ /**
57
+ * Function 2: Lee la bandeja y devuelve el email MÁS RECIENTE cuyo subject matchee con subjectRegex.
58
+ * Hace polling hasta timeout.
59
+ * @param subjectRegex Expresión regular a matchear con el asunto.
60
+ * @param timeoutMs Tiempo máximo a esperar (ms).
61
+ * @param pollMs Intervalo entre consultas (ms).
62
+ */
63
+ async readMailBySubject(subjectRegex, timeoutMs = 60000, pollMs = 5000) {
64
+ const start = Date.now();
65
+ while (Date.now() - start < timeoutMs) {
66
+ const { data } = await this.client.get(this.API_URL, {
67
+ params: {
68
+ f: 'check_email',
69
+ seq: this.seq,
70
+ ip: this.ip,
71
+ agent: this.agent,
72
+ },
73
+ });
74
+ this.seq = data.seq ?? this.seq;
75
+ const list = data.list ?? [];
76
+ // Filtrar emails que matchean el subject
77
+ const matches = list.filter(m => subjectRegex.test(m.mail_subject));
78
+ if (matches.length > 0) {
79
+ // Ordenar por timestamp descendente (más reciente primero)
80
+ matches.sort((a, b) => b.mail_timestamp - a.mail_timestamp);
81
+ // Tomar el más reciente
82
+ const mostRecent = matches[0];
83
+ const full = await this.fetchEmail(mostRecent.mail_id);
84
+ return full;
85
+ }
86
+ await new Promise(r => setTimeout(r, pollMs));
87
+ }
88
+ throw new Error(`Timeout (${timeoutMs} ms) esperando un email con asunto que matchee ${subjectRegex}`);
89
+ }
90
+ /**
91
+ * Function 3: Elimina un correo específico del servidor.
92
+ * @param mailId ID retornado por la API (mail_id).
93
+ */
94
+ async deleteMail(mailId) {
95
+ await this.client.get(this.API_URL, {
96
+ params: {
97
+ f: 'del_email',
98
+ email_ids: `[${mailId}]`, // formato aceptado por la API
99
+ ip: this.ip,
100
+ agent: this.agent,
101
+ },
102
+ });
103
+ }
104
+ /** Helper interno para descargar cuerpo completo */
105
+ async fetchEmail(mailId) {
106
+ const { data } = await this.client.get(this.API_URL, {
107
+ params: {
108
+ f: 'fetch_email',
109
+ email_id: mailId,
110
+ ip: this.ip,
111
+ agent: this.agent,
112
+ },
113
+ });
114
+ return data;
115
+ }
116
+ /**
117
+ * Function 4: Crea una nueva instancia de email client para aislamiento entre escenarios
118
+ */
119
+ static createIsolatedClient() {
120
+ return new GuerrillaMailClient();
121
+ }
122
+ /**
123
+ * Function 5: Elimina todos los emails del buzón actual
124
+ */
125
+ async clearInbox() {
126
+ const { data } = await this.client.get(this.API_URL, {
127
+ params: {
128
+ f: 'check_email',
129
+ seq: this.seq,
130
+ ip: this.ip,
131
+ agent: this.agent,
132
+ },
133
+ });
134
+ const list = data.list ?? [];
135
+ // Eliminar todos los emails uno por uno
136
+ for (const email of list) {
137
+ try {
138
+ await this.deleteMail(email.mail_id);
139
+ }
140
+ catch (error) {
141
+ console.warn(`Could not delete email ${email.mail_id}:`, error);
142
+ }
143
+ }
144
+ }
145
+ }
146
+ exports.GuerrillaMailClient = GuerrillaMailClient;
147
+ exports.email = new GuerrillaMailClient();
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "external-services-automation",
3
+ "version": "1.0.0",
4
+ "description": "External services automation library for Playwright and Cucumber",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "README.md",
10
+ "package.json"
11
+ ],
12
+ "keywords": [
13
+ "playwright",
14
+ "automation",
15
+ "testing",
16
+ "kinde",
17
+ "stripe",
18
+ "external-services"
19
+ ],
20
+ "author": "Email Awesome Team",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://bitbucket.org/connectist/external-services-automation.git"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "build:watch": "tsc --watch",
29
+ "clean": "rm -rf dist",
30
+ "prebuild": "npm run clean",
31
+ "test": "echo \"No tests specified\" && exit 0",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "peerDependencies": {
35
+ "@playwright/test": "^1.40.0",
36
+ "axios-cookiejar-support": "^6.0.2",
37
+ "dotenv": "^16.4.5",
38
+ "tough-cookie": "^5.1.2"
39
+ },
40
+ "devDependencies": {
41
+ "@playwright/test": "^1.40.0",
42
+ "@types/node": "^22.5.4",
43
+ "typescript": "^5.5.4"
44
+ }
45
+ }