Test Task
commit
2315dd4c49
@ -0,0 +1 @@
|
||||
cPanelLicensesPageUrl=https://store.cpanel.net/store/cpanel-licenses
|
||||
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
test-results/
|
||||
playwright-report/
|
||||
logs/
|
||||
@ -0,0 +1,11 @@
|
||||
export class cPanelLicensesPage {
|
||||
static orderNowButton = (index: number) => `(//*[@class='btn btn-success btn-sm btn-order-now'])[${index}]`;
|
||||
|
||||
static indexMap: { [key: string]: number } = {
|
||||
'cPanel Solo® Cloud (1 Account)': 1,
|
||||
'cPanel Admin Cloud (5 Accounts)': 2,
|
||||
'cPanel Pro Cloud (30 Accounts)': 3,
|
||||
'cPanel Premier (100 Accounts)': 4,
|
||||
'WP Squared': 5,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export class CheckoutPage {
|
||||
static personalInformationBlock = "(//div[@id='containerNewUserSignup']//div[@class='row'])[1]"
|
||||
static billingAddressBlock = "(//div[@id='containerNewUserSignup']//div[@class='row'])[2]"
|
||||
static accountSecurity = "//div[@id='containerNewUserSecurity']"
|
||||
static termsAndConditionsBlock = "//div[contains(@class, 'sub-heading') and .//span[text()='Terms & Conditions']]/following-sibling::div"
|
||||
static paymentDetailsBlock = "//*[@id='paymentGatewaysContainer'] | //*[@class='cc-input-container']"
|
||||
static completeOrderButton = "//*[@id='btnCompleteOrder']"
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
export class ProductConfigurePage {
|
||||
static ipAddressField = "//*[@class='field-container']//input";
|
||||
static ipAddressLoader = "//*[@class='float-right font-size-sm']";
|
||||
static addToCartButton = (index: number) => `(//*[@class='panel-add'])[${index}]`;
|
||||
static productNameInOrderSummaryBlock = `//*[@class='product-name']`;
|
||||
static addonNameInOrderSummaryBlock = `(//div[@id='producttotal']/div[@class='clearfix']//span[@class='pull-left float-left'])[2]`;
|
||||
static productPriceInOrderSummaryBlock = `(//div[@id='producttotal']/div[@class='clearfix']//span[@class='pull-right float-right'])[1]`;
|
||||
static addonPriceInOrderSummaryBlock= `(//div[@id='producttotal']/div[@class='clearfix']//span[@class='pull-right float-right'])[2]`
|
||||
static setupPriceInOrderSummaryBlock = `(//div[@class='summary-totals']//div[@class='clearfix']//span[@class='pull-right float-right'])[1]`;
|
||||
static monthlyPriceInOrderSummaryBlock = `(//div[@class='summary-totals']//div[@class='clearfix']//span[@class='pull-right float-right'])[2]`;
|
||||
static totalDueToday= `//*[@class='amt']`
|
||||
static continueButton = `//*[@type='submit']`
|
||||
|
||||
static indexMap: { [key: string]: number } = {
|
||||
'Monthly CloudLinux': 1,
|
||||
'Monthly CloudLinux for cPanel License': 2,
|
||||
'Monthly KernelCare License': 3,
|
||||
'LiteSpeed 8GB': 4,
|
||||
'LiteSpeed UNLIMITED': 5,
|
||||
'JetBackup': 6,
|
||||
'Monthly Imunify360 (Unlimited)': 7,
|
||||
'Monthly ImunifyAV+': 8,
|
||||
'WHMCS Plus': 9,
|
||||
'WHMCS Professional': 10,
|
||||
'WHMCS Business 1000': 11,
|
||||
'WHMCS Business 2500': 12,
|
||||
'WHMCS Business 5000': 13,
|
||||
'WHMCS Business 10000': 14,
|
||||
'WHMCS Business 50000': 15,
|
||||
'WHMCS Business 100000': 16,
|
||||
'WHMCS Business Unlimited': 17,
|
||||
};
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export class ReviewAndCheckoutPage {
|
||||
static productName = `(//*[@class='item-group'])[1]`
|
||||
static productMonthlyPrice = `(//*[@class='col-sm-4 item-price']/span[@class='cycle'])[1]`
|
||||
static addonMonthlyPrice = `(//*[@class='col-sm-4 item-price']/span[@class='cycle'])[2]`
|
||||
static ipAddress = `//*[@class='col-sm-7']/small`
|
||||
static totalDueToday = `//*[@class='total-due-today total-due-today-padded']`
|
||||
static checkoutButton = `//*[@id='checkout']`
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import {Page} from '@playwright/test';
|
||||
import {cPanelLicensesPage} from '../pages/cPanelLicensesPage';
|
||||
import {config} from 'dotenv';
|
||||
import {PageBase} from "../../utils/page-base";
|
||||
|
||||
config();
|
||||
|
||||
export class cPanelLicensesPageStepDefs extends PageBase {
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.navigateTo(process.env.cPanelLicensesPageUrl || '');
|
||||
}
|
||||
|
||||
async clickOnOrderNowLicense(product: string): Promise<void> {
|
||||
await this.page.click(cPanelLicensesPage.orderNowButton(cPanelLicensesPage.indexMap[product]));
|
||||
await this.page.waitForLoadState()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
import { Page, expect } from "@playwright/test";
|
||||
import { CheckoutPage } from "../pages/checkoutPage";
|
||||
import { PageBase } from "../../utils/page-base";
|
||||
|
||||
export class CheckoutPageStepDefs extends PageBase {
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
}
|
||||
|
||||
async assertPersonalInformationBlockIsVisible(): Promise<void> {
|
||||
await this.waitForElement(CheckoutPage.personalInformationBlock)
|
||||
await expect(this.page.locator(CheckoutPage.personalInformationBlock)).toBeVisible();
|
||||
}
|
||||
|
||||
async assertBillingAddressBlockIsVisible(): Promise<void> {
|
||||
await expect(this.page.locator(CheckoutPage.billingAddressBlock)).toBeVisible();
|
||||
}
|
||||
|
||||
async assertAccountSecurityBlockIsVisible(): Promise<void> {
|
||||
await expect(this.page.locator(CheckoutPage.accountSecurity)).toBeVisible();
|
||||
}
|
||||
|
||||
async assertTermsAndConditionsBlockIsVisible(): Promise<void> {
|
||||
await expect(this.page.locator(CheckoutPage.termsAndConditionsBlock)).toBeVisible();
|
||||
}
|
||||
|
||||
async assertPaymentDetailsBlockIsVisible(): Promise<void> {
|
||||
await expect(this.page.locator(CheckoutPage.paymentDetailsBlock).nth(0)).toBeVisible();
|
||||
await expect(this.page.locator(CheckoutPage.paymentDetailsBlock).nth(1)).toBeVisible();
|
||||
}
|
||||
|
||||
async assertCompleteOrderButtonIsVisibleAndDisabled(): Promise<void> {
|
||||
await expect(this.page.locator(CheckoutPage.completeOrderButton)).toBeVisible();
|
||||
await expect(this.page.locator(CheckoutPage.completeOrderButton)).toBeDisabled();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
import {expect, Page} from '@playwright/test';
|
||||
import {ProductConfigurePage} from "../pages/productConfigurePage";
|
||||
import {PageBase} from "../../utils/page-base";
|
||||
import {DateOperations} from "../../utils/dateOperations";
|
||||
|
||||
export class ProductConfigurePageStepDefs extends PageBase {
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
}
|
||||
|
||||
async fillIpAddressWithData(ipAddress: string): Promise<void> {
|
||||
await this.waitForElement(ProductConfigurePage.ipAddressField)
|
||||
await this.page.fill(ProductConfigurePage.ipAddressField, ipAddress);
|
||||
await this.page.keyboard.press('Enter');
|
||||
await this.waitForElement(ProductConfigurePage.ipAddressLoader, "hidden");
|
||||
}
|
||||
|
||||
async clickOnAddToCartProduct(product: string): Promise<void> {
|
||||
await this.page.click(ProductConfigurePage.addToCartButton(ProductConfigurePage.indexMap[product]));
|
||||
await this.waitForElement(ProductConfigurePage.addonNameInOrderSummaryBlock);
|
||||
}
|
||||
|
||||
async assertProductName(productName: string): Promise<void> {
|
||||
await expect(this.page.locator(ProductConfigurePage.productNameInOrderSummaryBlock)).toHaveText(productName);
|
||||
}
|
||||
|
||||
async assertAddonName(addonName: string): Promise<void> {
|
||||
await expect(this.page.locator(ProductConfigurePage.addonNameInOrderSummaryBlock)).toHaveText("+ " + addonName);
|
||||
}
|
||||
|
||||
async assertProductPrice(productPrice: string): Promise<void> {
|
||||
await expect(this.page.locator(ProductConfigurePage.productPriceInOrderSummaryBlock)).toHaveText(productPrice);
|
||||
}
|
||||
|
||||
async assertAddonPrice(addonPrice: string): Promise<void> {
|
||||
await expect(this.page.locator(ProductConfigurePage.addonPriceInOrderSummaryBlock)).toHaveText(addonPrice);
|
||||
}
|
||||
|
||||
async assertSetupFeePrice(feePrice: string): Promise<void> {
|
||||
await expect(this.page.locator(ProductConfigurePage.setupPriceInOrderSummaryBlock)).toHaveText(feePrice);
|
||||
}
|
||||
|
||||
async assertMonthlyPrice(): Promise<void> {
|
||||
const productPrice = await DateOperations.extractPrice(this.page, ProductConfigurePage.productPriceInOrderSummaryBlock);
|
||||
const addonPrice = await DateOperations.extractPrice(this.page, ProductConfigurePage.addonPriceInOrderSummaryBlock);
|
||||
const setupPrice = await DateOperations.extractPrice(this.page, ProductConfigurePage.setupPriceInOrderSummaryBlock);
|
||||
const monthlyPrice = await DateOperations.extractPrice(this.page, ProductConfigurePage.monthlyPriceInOrderSummaryBlock);
|
||||
|
||||
const totalCalculated = productPrice + addonPrice + setupPrice;
|
||||
|
||||
expect(totalCalculated).toBeCloseTo(monthlyPrice, 2);
|
||||
}
|
||||
|
||||
async assertTotalDue(): Promise<void> {
|
||||
const monthlyPrice = await DateOperations.extractPrice(this.page, ProductConfigurePage.monthlyPriceInOrderSummaryBlock);
|
||||
const totalDueToday = await DateOperations.extractPrice(this.page, ProductConfigurePage.totalDueToday);
|
||||
const expectedTotalDueToday = DateOperations.calculateTotalDue(monthlyPrice);
|
||||
|
||||
expect(totalDueToday).toBeCloseTo(expectedTotalDueToday, 2);
|
||||
}
|
||||
|
||||
|
||||
async clickOnContinue(): Promise<void> {
|
||||
await this.page.click(ProductConfigurePage.continueButton);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
import { expect, Page } from '@playwright/test';
|
||||
import { ReviewAndCheckoutPage } from "../pages/reviewAndCheckoutPage";
|
||||
import {DateOperations} from "../../utils/dateOperations";
|
||||
import {PageBase} from "../../utils/page-base";
|
||||
|
||||
|
||||
export class ReviewAndCheckoutPageStepDefs extends PageBase {
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
}
|
||||
|
||||
async assertLicenseName(productLicenseName: string): Promise<void> {
|
||||
await this.page.waitForLoadState()
|
||||
await this.waitForElement(ReviewAndCheckoutPage.productName)
|
||||
await expect(this.page.locator(ReviewAndCheckoutPage.productName)).toHaveText(productLicenseName);
|
||||
}
|
||||
|
||||
async assertIpAddress(ipAddress: string): Promise<void> {
|
||||
await this.waitForElement(ReviewAndCheckoutPage.productName)
|
||||
await expect(this.page.locator(ReviewAndCheckoutPage.ipAddress)).toHaveText("» IP Address: " + ipAddress);
|
||||
}
|
||||
|
||||
async assertProductMonthlyPrice(productMonthlyPrice: string): Promise<void> {
|
||||
await expect(this.page.locator(ReviewAndCheckoutPage.productMonthlyPrice)).toHaveText(productMonthlyPrice+ " Monthly");
|
||||
}
|
||||
|
||||
async assertAddonMonthlyPrice(addonMonthlyPrice: string): Promise<void> {
|
||||
await expect(this.page.locator(ReviewAndCheckoutPage.addonMonthlyPrice)).toHaveText(addonMonthlyPrice + " Monthly");
|
||||
}
|
||||
|
||||
async assertTotalDue(): Promise<void> {
|
||||
const productTotalDue = await DateOperations.extractPrice(this.page, ReviewAndCheckoutPage.productMonthlyPrice).then(DateOperations.calculateTotalDue);
|
||||
const addonTotalDue = await DateOperations.extractPrice(this.page, ReviewAndCheckoutPage.addonMonthlyPrice).then(DateOperations.calculateTotalDue);
|
||||
|
||||
|
||||
console.log(`Product Monthly Price: ${productTotalDue}`);
|
||||
console.log(`Addon Monthly Price: ${addonTotalDue}`);
|
||||
|
||||
const expectedTotalDueToday = productTotalDue + addonTotalDue;
|
||||
|
||||
const actualTotalDue = await DateOperations.extractPrice(this.page, ReviewAndCheckoutPage.totalDueToday)
|
||||
|
||||
expect(expectedTotalDueToday).toBeCloseTo(actualTotalDue, 2);
|
||||
|
||||
}
|
||||
|
||||
async clickOnCheckout(): Promise<void> {
|
||||
await this.page.click(ReviewAndCheckoutPage.checkoutButton)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import { Page } from '@playwright/test';
|
||||
|
||||
export class DateOperations {
|
||||
static async extractPrice(page: Page, locator: string): Promise<number> {
|
||||
const text = await page.locator(locator).textContent();
|
||||
|
||||
if (text === null) {
|
||||
throw new Error(`No text found in this locator: ${locator}`);
|
||||
}
|
||||
|
||||
return parseFloat(text.replace(/[^0-9.]/g, ''));
|
||||
}
|
||||
|
||||
static calculateTotalDue(monthlyPrice: number): number {
|
||||
const currentDate = new Date();
|
||||
const daysInMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getDate();
|
||||
const daysLeftInMonth = daysInMonth - currentDate.getDate() + 1;
|
||||
const dailyRate = monthlyPrice / daysInMonth;
|
||||
const totalDue = dailyRate * daysLeftInMonth;
|
||||
return parseFloat(totalDue.toFixed(2));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { Page } from '@playwright/test';
|
||||
import {ProductConfigurePage} from "../cPanel/pages/productConfigurePage";
|
||||
|
||||
export class PageBase {
|
||||
constructor(protected readonly page: Page) {}
|
||||
|
||||
async navigateTo(url: string): Promise<void> {
|
||||
await this.page.goto(url);
|
||||
}
|
||||
|
||||
async waitForElement(locator: string, state: 'attached' | 'detached' | 'visible' | 'hidden' = 'visible'): Promise<void> {
|
||||
await this.page.waitForSelector(locator, { state });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import { test } from '@playwright/test';
|
||||
import {cPanelLicensesPageStepDefs} from "../src/main/cPanel/step-definitions/cPanelLicensesPageStepDefs";
|
||||
import {ProductConfigurePageStepDefs} from "../src/main/cPanel/step-definitions/productConfigurePageStepDefs";
|
||||
import {ReviewAndCheckoutPageStepDefs} from "../src/main/cPanel/step-definitions/reviewAndCheckoutPageStepDefs";
|
||||
import {CheckoutPageStepDefs} from "../src/main/cPanel/step-definitions/checkoutPageStepDefs";
|
||||
|
||||
test.describe('Order License from cPanel Licenses Page with one Addon', () => {
|
||||
|
||||
test('cPanel Licenses Page', async ({ page }) => {
|
||||
const cPanelLicensesPage = new cPanelLicensesPageStepDefs(page);
|
||||
await cPanelLicensesPage.open();
|
||||
await cPanelLicensesPage.clickOnOrderNowLicense("cPanel Pro Cloud (30 Accounts)")
|
||||
const productConfigurePage = new ProductConfigurePageStepDefs(page)
|
||||
await productConfigurePage.fillIpAddressWithData("2.2.2.2")
|
||||
await productConfigurePage.clickOnAddToCartProduct("Monthly CloudLinux")
|
||||
await productConfigurePage.assertProductName("cPanel Pro Cloud (30 Accounts)")
|
||||
await productConfigurePage.assertAddonName("Monthly CloudLinux")
|
||||
await productConfigurePage.assertProductPrice("$42.99 USD")
|
||||
await productConfigurePage.assertAddonPrice("$26.00 USD")
|
||||
await productConfigurePage.assertSetupFeePrice("$0.00 USD")
|
||||
await productConfigurePage.assertMonthlyPrice()
|
||||
await productConfigurePage.assertTotalDue()
|
||||
await productConfigurePage.clickOnContinue()
|
||||
const reviewAndCheckoutPage = new ReviewAndCheckoutPageStepDefs(page)
|
||||
await reviewAndCheckoutPage.assertLicenseName("cPanel Licenses")
|
||||
await reviewAndCheckoutPage.assertIpAddress("2.2.2.2")
|
||||
// await reviewAndCheckoutPage.assertProductMonthlyPrice("$42.99 USD")
|
||||
await reviewAndCheckoutPage.assertAddonMonthlyPrice("$26.00 USD")
|
||||
// await reviewAndCheckoutPage.assertTotalDue()
|
||||
await reviewAndCheckoutPage.clickOnCheckout()
|
||||
const checkoutPage = new CheckoutPageStepDefs(page)
|
||||
await checkoutPage.assertPersonalInformationBlockIsVisible()
|
||||
await checkoutPage.assertBillingAddressBlockIsVisible()
|
||||
await checkoutPage.assertAccountSecurityBlockIsVisible()
|
||||
await checkoutPage.assertTermsAndConditionsBlockIsVisible()
|
||||
await checkoutPage.assertPaymentDetailsBlockIsVisible()
|
||||
await checkoutPage.assertCompleteOrderButtonIsVisibleAndDisabled()
|
||||
|
||||
});
|
||||
|
||||
})
|
||||
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"baseUrl": "./",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./src/types" // Add the path to your declaration files here
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/types/**/*" // Ensure this is included if declaration files are in a different directory
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue