Skip to content

Commit

Permalink
feat: add contact form page object and other corresponding locator ad…
Browse files Browse the repository at this point in the history
…dings (#294)
  • Loading branch information
frobel authored Feb 3, 2025
1 parent b58761b commit 38983bf
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 3 deletions.
8 changes: 7 additions & 1 deletion src/page-objects/StorefrontPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SearchSuggest } from './storefront/SearchSuggest';
import { CustomRegister } from './storefront/CustomRegister';
import { CheckoutOrderEdit } from './storefront/CheckoutOrderEdit';
import { AccountAddressCreate } from './storefront/AccountAddresssCreate';
import { ContactForm } from './storefront/ContactForm';

export interface StorefrontPageTypes {
StorefrontHome: Home;
Expand All @@ -43,6 +44,7 @@ export interface StorefrontPageTypes {
StorefrontSearchSuggest: SearchSuggest;
StorefrontCustomRegister: CustomRegister;
StorefrontCheckoutOrderEdit: CheckoutOrderEdit;
StorefrontContactForm: ContactForm;
}

export const StorefrontPageObjects = {
Expand All @@ -66,6 +68,7 @@ export const StorefrontPageObjects = {
SearchSuggest,
CustomRegister,
CheckoutOrderEdit,
ContactForm,
}

export const test = base.extend<FixtureTypes>({
Expand Down Expand Up @@ -144,10 +147,13 @@ export const test = base.extend<FixtureTypes>({

StorefrontCustomRegister: async ({ StorefrontPage }, use) => {
await use(new CustomRegister(StorefrontPage));

},

StorefrontCheckoutOrderEdit: async ({ StorefrontPage }, use) => {
await use(new CheckoutOrderEdit(StorefrontPage));
},

StorefrontContactForm: async ({ StorefrontPage }, use) => {
await use(new ContactForm(StorefrontPage));
},
});
13 changes: 13 additions & 0 deletions src/page-objects/storefront/AccountLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ export class AccountLogin implements PageObject {
public readonly postalCodeInput: Locator;
public readonly registerButton: Locator;

// Inputs for reCaptcha
public readonly greCaptchaV2Container: Locator;
public readonly greCaptchaV2Input: Locator;
public readonly greCaptchaV3Input: Locator;
public readonly greCaptchaProtectionInformation: Locator;
public readonly greCaptchaBadge: Locator;

constructor(public readonly page: Page) {
this.emailInput = page.getByLabel('Your email address');
this.passwordInput = page.getByLabel('Your password');
Expand Down Expand Up @@ -56,6 +63,12 @@ export class AccountLogin implements PageObject {
this.logoutLink = page.getByRole('link', { name: 'Log out'});
this.successAlert = page.getByText('Successfully logged out.');
this.passwordUpdatedAlert = page.getByText('Your password has been updated.');

this.greCaptchaV2Container = this.page.locator('.grecaptcha-v2-container');
this.greCaptchaV2Input = this.page.locator('.grecaptcha-v2-input');
this.greCaptchaV3Input = this.page.locator('.grecaptcha_v3-input');
this.greCaptchaProtectionInformation = this.page.locator('.grecaptcha-protection-information');
this.greCaptchaBadge = this.page.locator('.grecaptcha-badge');
}

url() {
Expand Down
57 changes: 57 additions & 0 deletions src/page-objects/storefront/ContactForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Page, Locator } from '@playwright/test';
import type { PageObject } from '../../types/PageObject';
import { Home } from './Home';

export class ContactForm extends Home implements PageObject {
public readonly contactModal: Locator;
public readonly contactSuccessModal: Locator;
public readonly salutationSelect: Locator;
public readonly firstNameInput: Locator;
public readonly lastNameInput: Locator;
public readonly emailInput: Locator;
public readonly phoneInput: Locator;
public readonly subjectInput: Locator;
public readonly commentInput: Locator;
public readonly privacyPolicyCheckbox: Locator;
public readonly submitButton: Locator;
public readonly contactSuccessMessage: Locator;
public readonly cardTitle: Locator;
/**
* Captcha locators
*/
public readonly basicCaptcha: Locator;
public readonly basicCaptchaImage: Locator;
public readonly basicCaptchaRefreshButton: Locator;
public readonly basicCaptchaInput: Locator;
public readonly greCaptchaV2Container: Locator;
public readonly greCaptchaV2Input: Locator;
public readonly greCaptchaProtectionInformation: Locator;

constructor(public readonly page: Page) {
super(page);
this.contactModal = this.page.getByRole('dialog').filter({ has: this.page.getByLabel('Contact', { exact: true }) });
this.salutationSelect = this.contactModal.getByLabel('Salutation*');
this.firstNameInput = this.contactModal.getByLabel('First name*');
this.lastNameInput = this.contactModal.getByLabel('Last name*');
this.emailInput = this.contactModal.getByLabel('Your email address*');
this.phoneInput = this.contactModal.getByLabel('Phone*');
this.subjectInput = this.contactModal.getByLabel('Subject*');
this.commentInput = this.contactModal.getByLabel('Comment*');
this.privacyPolicyCheckbox = this.contactModal.getByRole('checkbox', { name: 'By selecting continue you confirm that you have read and agree to our' });
this.submitButton = this.contactModal.getByRole('button', { name: 'Submit' });
this.contactSuccessModal = this.page.getByRole('dialog').filter({ has: this.page.locator('.confirm-message') });
this.contactSuccessMessage = this.contactSuccessModal.locator('.confirm-message');
this.cardTitle = this.contactModal.locator('.card-title');
this.basicCaptcha = this.contactModal.locator('.basic-captcha');
this.basicCaptchaImage = this.basicCaptcha.locator('img');
this.basicCaptchaRefreshButton = this.basicCaptcha.locator('.basic-captcha-content-refresh-icon');
this.basicCaptchaInput = this.basicCaptcha.locator('input[name="shopware_basic_captcha_confirm"]');
this.greCaptchaV2Container = this.contactModal.locator('.grecaptcha-v2-container');
this.greCaptchaV2Input = this.contactModal.locator('.grecaptcha-v2-input');
this.greCaptchaProtectionInformation = this.contactModal.locator('.grecaptcha-protection-information');
}

url() {
return new Error('Function not implemented, because it is a modal page object').message;
}
}
3 changes: 3 additions & 0 deletions src/page-objects/storefront/Home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class Home implements PageObject {
public readonly consentDialog: Locator;
public readonly consentDialogTechnicallyRequiredCheckbox: Locator;
public readonly consentDialogStatisticsCheckbox: Locator;
public readonly contactFormLink: Locator;

/**
* @deprecated Use 'consentDialogMarketingCheckbox' instead
*/
Expand Down Expand Up @@ -63,6 +65,7 @@ export class Home implements PageObject {
exact: true,
});
this.offcanvasBackdrop = page.locator('.offcanvas-backdrop');
this.contactFormLink = this.page.getByRole('listitem').getByTitle('Contact form', { exact: true });
}

async getMenuItemByCategoryName(categoryName: string): Promise<Record<string, Locator>> {
Expand Down
45 changes: 43 additions & 2 deletions src/services/TestDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
CustomFieldSet,
CustomField,
Tax,
ProductCrossSelling,
} from '../types/ShopwareTypes';
import { expect } from '@playwright/test';

Expand Down Expand Up @@ -100,7 +101,7 @@ export class TestDataService {
*
* @private
*/
private highPriorityEntities = ['order', 'product', 'landing_page', 'shipping_method', 'sales_channel_domain', 'sales_channel_currency', 'sales_channel_country', 'customer'];
private highPriorityEntities = ['order', 'product_cross_selling' , 'product', 'landing_page', 'shipping_method', 'sales_channel_domain', 'sales_channel_currency', 'sales_channel_country', 'customer'];

/**
* A registry of all created records.
Expand Down Expand Up @@ -475,6 +476,27 @@ export class TestDataService {
return propertyGroup;
}

/**
* Creates a basic product cross-selling entity without products.
*
* @param productId - The uuid of the product to which the pproduct cross-selling should be assigned.
* @param overrides - Specific data overrides that will be applied to the property group data struct.
*/
async createProductCrossSelling(productId: string, overrides: Partial<ProductCrossSelling> = {}): Promise<ProductCrossSelling> {
const crossSellingStruct = this.getBasicCrossSellingStruct(productId, overrides);

const response = await this.AdminApiClient.post('product-cross-selling?_response=detail', {
data: crossSellingStruct,
});
expect(response.ok()).toBeTruthy();

const { data: productCrossSelling } = (await response.json()) as { data: ProductCrossSelling };

this.addCreatedRecord('product_cross_selling', productCrossSelling.id);

return productCrossSelling;
}

/**
* Creates a new tag which can be assigned to other entities.
*
Expand Down Expand Up @@ -1640,7 +1662,6 @@ export class TestDataService {
deleteOperations[`delete-${record.resource}`].payload.push(record.payload);
}
});

await this.AdminApiClient.post('_action/sync', {
data: priorityDeleteOperations,
});
Expand Down Expand Up @@ -2516,4 +2537,24 @@ export class TestDataService {

return Object.assign({}, basicTaxStruct, overrides);
}

getBasicCrossSellingStruct(productId: string, overrides: Partial<ProductCrossSelling> = {}) {

const { id: productCrossSellingId, uuid: productCrossSellingUuid } = this.IdProvider.getIdPair();
const productCrossSellingName = `${this.namePrefix}ProductCrossSelling-${productCrossSellingId}${this.nameSuffix}`;

const defaultCrossSelling = {
id: productCrossSellingUuid,
productId: productId,
name: productCrossSellingName,
type: 'product_list',
position: 1,
active: true,
productStreamId: null,
sortingType: 'name',
limit: 10,
sortBy: 'name',
}
return Object.assign({}, defaultCrossSelling, overrides);
}
}
4 changes: 4 additions & 0 deletions src/types/ShopwareTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ export type SalesChannelAnalytics = components['schemas']['SalesChannelAnalytics
id: string,
};

export type ProductCrossSelling = components['schemas']['ProductCrossSelling'] & {
id: string,
};

export interface RegistrationData {
isCommercial: boolean;
isGuest: boolean;
Expand Down
9 changes: 9 additions & 0 deletions tests/TestDataService/TestDataService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
APIResponse,
SalesChannelAnalytics,
Tax,
ProductCrossSelling,
} from '../../src';

test('Data Service', async ({
Expand Down Expand Up @@ -93,6 +94,10 @@ test('Data Service', async ({
const taxRate21 = await TestDataService.createTaxRate({ taxRate: 21.0 });
expect(taxRate21.taxRate).toEqual(21.0);

const crossSellingProduct = await TestDataService.createBasicProduct();
const productCrossSelling = await TestDataService.createProductCrossSelling(crossSellingProduct.id, { name: 'Custom cross selling' });
expect(productCrossSelling.name).toEqual('Custom cross selling');

// Test data clean-up with deactivated cleansing process
TestDataService.setCleanUp(false);
const cleanUpFalseResponse = await TestDataService.cleanUp();
Expand Down Expand Up @@ -150,6 +155,10 @@ test('Data Service', async ({
const { data: databaseTaxRate21 } = (await taxRate21Response.json()) as { data: Tax };
expect(databaseTaxRate21.id).toBe(taxRate21.id);

const crossSellingProductResponse = await AdminApiContext.get(`./product-cross-selling/${productCrossSelling.id}?_response=detail`);
const { data: databaseCrossSellingProduct } = (await crossSellingProductResponse.json()) as { data: ProductCrossSelling };
expect(databaseCrossSellingProduct.id).toBe(productCrossSelling.id);

// Test data clean-up with activated cleansing process
TestDataService.setCleanUp(true);
const cleanUpDeleteOperationsResponse = await TestDataService.cleanUp() as APIResponse;
Expand Down

0 comments on commit 38983bf

Please sign in to comment.