Skip to content
Merged
3 changes: 3 additions & 0 deletions lib/helpers/ApiHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {MemberApiHelper} from './MemberApiHelper';
import {MemberTypeApiHelper} from "./MemberTypeApiHelper";
import {DocumentBlueprintApiHelper} from "./DocumentBlueprintApiHelper";
import {LoginApiHelper} from "./LoginApiHelper";
import {WebhookApiHelper} from "./WebhookApiHelper";

export class ApiHelpers {
baseUrl: string = umbracoConfig.environment.baseUrl;
Expand Down Expand Up @@ -67,6 +68,7 @@ export class ApiHelpers {
memberType: MemberTypeApiHelper;
documentBlueprint: DocumentBlueprintApiHelper;
login: LoginApiHelper;
webhook: WebhookApiHelper;

constructor(page: Page) {
this.page = page;
Expand Down Expand Up @@ -101,6 +103,7 @@ export class ApiHelpers {
this.memberType = new MemberTypeApiHelper(this);
this.documentBlueprint = new DocumentBlueprintApiHelper(this);
this.login = new LoginApiHelper(this, this.page);
this.webhook = new WebhookApiHelper(this, this.page);
}

async getAccessToken() {
Expand Down
28 changes: 28 additions & 0 deletions lib/helpers/ConstantHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,32 @@
referenceHeadline: 'The following items depend on this',
bulkReferenceHeadline: 'The following items are used by other content.'
}

public static readonly webhookEvents = [
{
"eventName": "Content Deleted",
"eventType": "Content",
"alias": "Umbraco.ContentDelete"
},
{
"eventName": "Content Published",
"eventType": "Content",
"alias": "Umbraco.ContentPublish"
},
{
"eventName": "Content Unpublished",
"eventType": "Content",
"alias": "Umbraco.ContentUnpublish"
},
{
"eventName": "Media Deleted",
"eventType": "Media",
"alias": "Umbraco.MediaDelete"
},
{
"eventName": "Media Saved",
"eventType": "Media",
"alias": "Umbraco.MediaSave"
}
]
}
2 changes: 1 addition & 1 deletion lib/helpers/ContentUiHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export class ContentUiHelper extends UiBaseLocators {
this.documentBlueprintModal = page.locator('umb-create-blueprint-modal');
this.documentBlueprintModalEnterNameTxt = this.documentBlueprintModal.locator('input');
this.documentBlueprintSaveBtn = this.documentBlueprintModal.getByLabel('Save');
this.emptyRecycleBinBtn = page.getByLabel('Empty Recycle Bin');
this.emptyRecycleBinBtn = page.getByTestId('entity-action:Umb.EntityAction.Document.RecycleBin.Empty').locator('#button');
this.confirmEmptyRecycleBinBtn = page.locator('#confirm').getByLabel('Empty Recycle Bin', {exact: true});
this.duplicateToBtn = page.getByRole('button', {name: 'Duplicate to'});
this.moveToBtn = page.getByRole('button', {name: 'Move to'});
Expand Down
6 changes: 5 additions & 1 deletion lib/helpers/UiBaseLocators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ export class UiBaseLocators {
}

async clickRemoveButtonForName(name: string) {
await this.page.locator('[name="' + name + '"] [Label="Remove"]').click();
await this.page.locator('[name="' + name + '"] [label="Remove"]').click();
}

async clickTrashIconButtonForName(name: string) {
Expand Down Expand Up @@ -1323,4 +1323,8 @@ export class UiBaseLocators {
await expect(this.sidebarModal.locator('uui-menu-item[label="' + name + '"]')).toBeVisible();
await this.sidebarModal.locator('uui-menu-item[label="' + name + '"]').click();
}

async isModalMenuItemWithNameDisabled(name: string) {
await expect(this.sidebarModal.locator('uui-menu-item[label="' + name + '"]')).toHaveAttribute('disabled');
}
}
3 changes: 3 additions & 0 deletions lib/helpers/UiHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {DictionaryUiHelper} from "./DictionaryUiHelper";
import {ContentRenderUiHelper} from './ContentRenderUiHelper';
import {FormsUiHelper} from "./FormsUiHelper";
import {CurrentUserProfileUiHelper} from './CurrentUserProfileUiHelper';
import {WebhookUiHelper} from "./WebhookUiHelper";

export class UiHelpers {
page: Page;
Expand Down Expand Up @@ -67,6 +68,7 @@ export class UiHelpers {
contentRender: ContentRenderUiHelper;
form: FormsUiHelper;
currentUserProfile: CurrentUserProfileUiHelper;
webhook: WebhookUiHelper;

constructor(page: Page) {
this.page = page;
Expand Down Expand Up @@ -102,6 +104,7 @@ export class UiHelpers {
this.contentRender = new ContentRenderUiHelper(this.page);
this.form = new FormsUiHelper(this.page);
this.currentUserProfile = new CurrentUserProfileUiHelper(this.page);
this.webhook = new WebhookUiHelper(this.page);
}

async goToBackOffice() {
Expand Down
198 changes: 198 additions & 0 deletions lib/helpers/WebhookApiHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import {expect, Page} from "@playwright/test";
import {ApiHelpers} from "./ApiHelpers";
import {WebhookBuilder} from "@umbraco/json-models-builders";
import {ConstantHelper} from "./ConstantHelper";

export class WebhookApiHelper {
api: ApiHelpers;
page: Page;
webhookSiteUrl: string = 'https://webhook.site/';

constructor(api: ApiHelpers, page: Page) {
this.api = api;
this.page = page;
}

async get(id: string) {
const response = await this.api.get(this.api.baseUrl + '/umbraco/management/api/v1/webhook/' + id);
const json = await response.json();

if (json !== null) {
return json;
}
return null;
}

async doesExist(id: string) {
const response = await this.get(this.api.baseUrl + '/umbraco/management/api/v1/webhook/' + id);
return response.status() === 200;
}

async create(webhookData) {
if (webhookData == null) {
return;
}
const response = await this.api.post(this.api.baseUrl + '/umbraco/management/api/v1/webhook', webhookData);
// Returns the id of the created webhook
return response.headers().location.split("/").pop();
}

async delete(id: string) {
return await this.api.delete(this.api.baseUrl + '/umbraco/management/api/v1/webhook/' + id);
}

async update(webhook: object) {
return await this.api.put(this.api.baseUrl + '/umbraco/management/api/v1/webhook/', webhook);
}

async getAll() {
return await this.api.get(this.api.baseUrl + '/umbraco/management/api/v1/webhook?pageSize=50&skip=0&take=10000');
}

async doesNameExist(name: string) {
return await this.getByName(name);
}

async getByName(name: string) {
const allWebhooks = await this.getAll();
const jsonWebhooks = await allWebhooks.json();

for (const webhook of jsonWebhooks.items) {
if (webhook.name === name) {
return await this.get(webhook.id);
}
}
return null;
}

async ensureNameNotExists(name: string) {
const allWebhooks = await this.getAll();
const jsonWebhooks = await allWebhooks.json();

for (const webhook of jsonWebhooks.items) {
if (webhook.name === name) {
return await this.delete(webhook.id);
}
}
return null;
}

async generateWebhookSiteToken() {
const createWebhookResponse = await this.page.request.post(this.webhookSiteUrl + 'token', {
headers: {
'Accept': 'application/json'
}
});
const webhookData = await createWebhookResponse.json();
const webhookToken = webhookData.uuid;
return webhookToken;
}

async getWebhookSiteRequestResponse(webhookSiteToken: string, timeoutMs: number = 15000, pollInterval: number = 1000) {
const requestUrl = this.webhookSiteUrl + 'token/' + webhookSiteToken + '/requests';
const start = Date.now();

while (Date.now() - start < timeoutMs) {
const requestResponse = await this.page.request.get(requestUrl, {
headers: {
'Accept': 'application/json'
}
});
const requestJson = await requestResponse.json();
if (requestJson.total > 0) {
return requestJson.data;
}

// Polling again if there is no webhook received yet
await new Promise((resolve) => setTimeout(resolve, pollInterval));
}
return null;
}

async doesWebhookHaveEvent(webhookName: string, eventName: string) {
const webhookData = await this.getByName(webhookName);
return webhookData.events.find(event => event.eventName === eventName);
}

async doesWebhookHaveContentTypeId(webhookName: string, contentTypeId: string) {
const webhookData = await this.getByName(webhookName);
return webhookData.contentTypeKeys.includes(contentTypeId);
}

async doesWebhookHaveHeader(webhookName: string, headerName: string, headerValue: string) {
const webhookData = await this.getByName(webhookName);
return webhookData.headers[headerName] === headerValue;
}

async doesWebhookHaveUrl(webhookName: string, url: string) {
const webhookData = await this.getByName(webhookName);
return webhookData.url === url;
}

async isWebhookEnabled(webhookName: string, isEnabled: boolean = true) {
const webhookData = await this.getByName(webhookName);
return expect(webhookData.enabled).toBe(isEnabled);
}

async getEventTypeValue(eventName: string) {
const eventData = ConstantHelper.webhookEvents.find(event => event.eventName === eventName);
return eventData?.eventType || '';
}

async getEventAliasValue(eventName: string) {
const eventData = ConstantHelper.webhookEvents.find(event => event.eventName === eventName);
return eventData?.alias || '';
}

async createDefaultWebhook(webhookName: string, webhookSiteToken: string, eventName: string = 'Content Published', isEnabled: boolean = true) {
await this.ensureNameNotExists(webhookName);
const webhookUrl = this.webhookSiteUrl + webhookSiteToken;
const eventAlias = await this.getEventAliasValue(eventName);

const webhook = new WebhookBuilder()
.withName(webhookName)
.withUrl(webhookUrl)
.withEventAlias(eventAlias)
.withEnabled(isEnabled)
.build();

return await this.create(webhook);
}

async createWebhookForSpecificContentType(webhookName: string, webhookSiteToken: string, eventName: string, contentTypeName: string) {
await this.ensureNameNotExists(webhookName);
const webhookUrl = this.webhookSiteUrl + webhookSiteToken;
const eventAlias = await this.getEventAliasValue(eventName);
const eventType = await this.getEventTypeValue(eventName);
let contentTypeData;
if (eventType == 'Content') {
contentTypeData = await this.api.documentType.getByName(contentTypeName);
} else {
contentTypeData = await this.api.mediaType.getByName(contentTypeName);
}

const webhook = new WebhookBuilder()
.withName(webhookName)
.withUrl(webhookUrl)
.withEventAlias(eventAlias)
.withContentTypeKey(contentTypeData.id)
.build();

return await this.create(webhook);
}

async createWebhookWithHeader(webhookName: string, webhookSiteToken: string, eventName: string, headerName: string, headerValue: string) {
await this.ensureNameNotExists(webhookName);
const webhookUrl = this.webhookSiteUrl + webhookSiteToken;
const eventAlias = await this.getEventAliasValue(eventName);

const webhook = new WebhookBuilder()
.withName(webhookName)
.withUrl(webhookUrl)
.withEventAlias(eventAlias)
.withHeader(headerName, headerValue)
.build();

return await this.create(webhook);
}
}
Loading