Skip to content

Commit

Permalink
Tests - triggers, webhooks (#901)
Browse files Browse the repository at this point in the history
* e2e tests - api helpers - trigger, webhook

* [WIP] E2E tests - triggers, webhooks

* tests - webhooks - WIP

* E2E test for webhooks - complete

* CreateTriggerPage

* Complete trigger test

* Selectors updated, Cleanup

* selectors updated after data-test, Fixes after eslint
  • Loading branch information
tkonieczny authored Oct 11, 2023
1 parent 905361d commit 3894a79
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 6 deletions.
21 changes: 21 additions & 0 deletions packages/e2e-tests/fixtures/triggers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import config from '../config';

export default {
'trigger-deployment-creation-name-test-name': {
name: 'temp-trigger-depl-cr-name-test-name',
namespace: config.namespace,
resource: 'deployment',
event: 'created',
action: 'run',
execution: 'test',
concurrencyPolicy: '',
testSelector: {
name: 'postman-executor-smoke',
namespace: 'testkube',
},
resourceSelector: {
name: 'non-existant-resource',
namespace: 'namespace-name',
},
},
};
14 changes: 14 additions & 0 deletions packages/e2e-tests/fixtures/webhooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import config from '../config';

export default {
'temp-wh-on-test-start': {
name: 'example-webhook3',
namespace: config.namespace,
events: ['start-test'],
selector: {
asdf: 'asdf', // request: 'asdf=asdf'
bbb: 'ccc',
},
uri: 'http://webhook-url.example.com',
},
};
72 changes: 71 additions & 1 deletion packages/e2e-tests/helpers/api-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import superagent from 'superagent';

import type {ExecutorData, TestData, TestExecutionListResponse, TestSourceData, TestSuiteData} from '../types';
import type {
ExecutorData,
TestData,
TestExecutionListResponse,
TestSourceData,
TestSuiteData,
TriggerData,
WebhookData,
} from '../types';

export class ApiHelpers {
public apiUrl: string;
Expand Down Expand Up @@ -84,6 +92,14 @@ export class ApiHelpers {
return this.makeGet(`${this.apiUrl}/executors`);
}

public async getTriggers(): Promise<TriggerData[]> {
return this.makeGet(`${this.apiUrl}/triggers`);
}

public async getWebhooks(): Promise<WebhookData[]> {
return this.makeGet(`${this.apiUrl}/webhooks`);
}

public async createTest(testData: Partial<TestData>): Promise<TestData> {
return this.makePost(`${this.apiUrl}/tests`, testData);
}
Expand Down Expand Up @@ -112,6 +128,14 @@ export class ApiHelpers {
return this.makeDelete(`${this.apiUrl}/executors/${executorName}`);
}

public async removeTrigger(triggerName: string): Promise<any> {
return this.makeDelete(`${this.apiUrl}/triggers/${triggerName}`);
}

public async removeWebhook(webhookName: string): Promise<any> {
return this.makeDelete(`${this.apiUrl}/webhooks/${webhookName}`);
}

public async updateTest(testData: Partial<TestData>): Promise<any> {
return this.makePatch(`${this.apiUrl}/tests/${testData.name}`, testData);
}
Expand Down Expand Up @@ -156,6 +180,24 @@ export class ApiHelpers {
}
}

public async isTriggerCreated(triggerName: string): Promise<boolean> {
try {
const currentTriggers = await this.getTriggers();
return currentTriggers.some(({name}) => name === triggerName);
} catch (e) {
throw Error(`isTriggerCreated failed for "${triggerName}" with: "${e}"`);
}
}

public async isWebhookCreated(webhookName: string): Promise<boolean> {
try {
const currentWebhooks = await this.getWebhooks();
return currentWebhooks.some(({name}) => name === webhookName);
} catch (e) {
throw Error(`isWebhookCreated failed for "${webhookName}" with: "${e}"`);
}
}

public async assureTestNotCreated(testName: string): Promise<void> {
try {
if (await this.isTestCreated(testName)) {
Expand Down Expand Up @@ -197,6 +239,26 @@ export class ApiHelpers {
}
}

public async assureTriggerNotCreated(triggerName: string): Promise<void> {
try {
if (await this.isTriggerCreated(triggerName)) {
await this.removeTrigger(triggerName);
}
} catch (e) {
throw Error(`assureTriggerNotCreated failed for "${triggerName}" with: "${e}"`);
}
}

public async assureWebhookNotCreated(webhookName: string): Promise<void> {
try {
if (await this.isWebhookCreated(webhookName)) {
await this.removeWebhook(webhookName);
}
} catch (e) {
throw Error(`assureWebhookNotCreated failed for "${webhookName}" with: "${e}"`);
}
}

public async assureTestCreated(testData: Partial<TestData>, fullCleanup = false): Promise<void> {
try {
if (await this.isTestCreated(testData.name)) {
Expand Down Expand Up @@ -244,6 +306,14 @@ export class ApiHelpers {
return this.makeGet(`${this.apiUrl}/executors/${executorName}`);
}

public async getTriggerData(triggerName: string): Promise<TestData> {
return this.makeGet(`${this.apiUrl}/triggers/${triggerName}`);
}

public async getWebhookData(webhookName: string): Promise<TestData> {
return this.makeGet(`${this.apiUrl}/webhooks/${webhookName}`);
}

public async getTestExecutions(testName: string): Promise<TestExecutionListResponse> {
return this.makeGet(`${this.apiUrl}/tests/${testName}/executions`);
}
Expand Down
12 changes: 11 additions & 1 deletion packages/e2e-tests/helpers/common.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {expect} from '@playwright/test';

import {TestDataHandler} from '../helpers/test-data-handler';
import type {ExecutorData, TestData, TestSourceData, TestSuiteData} from '../types';
import type {ExecutorData, TestData, TestSourceData, TestSuiteData, TriggerData, WebhookData} from '../types';

export function validateLabels(labels: Record<string, string>, createdLabels: Record<string, string>): void {
Object.entries(labels).forEach(([name, value]) => {
Expand Down Expand Up @@ -50,3 +50,13 @@ export function validateExecutor(executorData: Partial<ExecutorData>, createdExe
expect(executorData.types).toEqual(createdExecutorData.executor.types);
expect(executorData.executorType).toEqual(createdExecutorData.executor.executorType);
}

export function validateWebhook(webhookData: Partial<WebhookData>, createdWebhookData: WebhookData): void {
const processedWebhookData = TestDataHandler.getProcessedWebhookData(webhookData);

expect(processedWebhookData).toEqual(createdWebhookData);
}

export function validateTrigger(triggerData: Partial<TriggerData>, createdTriggerData: TriggerData): void {
expect(triggerData).toEqual(createdTriggerData);
}
32 changes: 32 additions & 0 deletions packages/e2e-tests/helpers/test-data-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import executorData from '../fixtures/executors';
import testsData from '../fixtures/tests';
import testSourcesData from '../fixtures/testsources';
import testSuitesData from '../fixtures/testsuites';
import triggersData from '../fixtures/triggers';
import webhooksData from '../fixtures/webhooks';
import type {WebhookData} from '../types';

// TODO: Use functions instead
export class TestDataHandler {
Expand Down Expand Up @@ -59,6 +62,35 @@ export class TestDataHandler {
};
}

public getTrigger(triggerName: string): any {
let trigger = (triggersData as any)[triggerName];

return {
...trigger,
name: this.getRandomizedName(trigger.name),
};
}

public getWebhook(webhookName: string): any {
let webhook = (webhooksData as any)[webhookName];

return {
...webhook,
name: this.getRandomizedName(webhook.name),
};
}

public static getProcessedWebhookData(webhookData: Partial<WebhookData>): Partial<WebhookData> {
const webhookSelector = Object.entries(webhookData.selector)
.map(entry => entry.join('='))
.join(',');

return {
...webhookData,
selector: webhookSelector,
};
}

public static getFixtureFileContents(fixtureFileName: string) {
return readFileSync(`fixtures/files/${fixtureFileName}`).toString();
}
Expand Down
77 changes: 77 additions & 0 deletions packages/e2e-tests/pages/CreateTriggerPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type {Page} from '@playwright/test';

import type {TriggerData} from '../types';

export class CreateTriggerPage {
public readonly page: Page;

public constructor(page: Page) {
this.page = page;
}

public async createTrigger(triggerData: Partial<TriggerData>): Promise<void> {
await this.setName(triggerData.name);
await this.setResource(triggerData.resource);
await this.setResourceSelector(triggerData.resourceSelector);
await this.setTriggerEvent(triggerData.event);
await this.clickNextButton();
await this.setTriggerAction(triggerData.action, triggerData.execution);
await this.setTestSelector(triggerData.testSelector);
await this.clickCreateButton();
}

async setName(triggerName: string) {
await this.page.locator(`xpath=//input[@data-test="triggers-add-modal-name"]`).fill(triggerName);
}

async setResource(resourceType: string) {
await this.page.click(
`xpath=//div[@data-test="triggers-add-modal-condition-resource"]//div[contains(@class,"control-input-content")]`
);
await this.page.click(`xpath=//div[contains(@class,"ant-select-item-option") and @title="${resourceType}"]`); // TODO: data-test (for rc-virtual-list)
}

async setResourceSelector(resourceSelector: {name: string; namespace: string}) {
if (resourceSelector.name) {
const resourceSelectorString = `${resourceSelector.namespace}/${resourceSelector.name}`;
await this.page.click(
`xpath=//div[@data-test="triggers-add-modal-condition-selector-switch"]//div[@title="BY NAME"]`
);
await this.page
.locator(`xpath=//input[@id="add-trigger-form_resourceNameSelector"]`)
.fill(resourceSelectorString);
}
}

async setTriggerEvent(triggerEvent: string) {
await this.page.click(`xpath=//input[@id="add-trigger-form_event"]`);
await this.page.click(
`xpath=//div[@class="rc-virtual-list"]//div[contains(@class,"item-option") and @title="${triggerEvent}"]`
); // TODO: data-test (for rc-virtual-list)
}

async clickNextButton() {
await this.page.click(`xpath=//button[@data-test="triggers-add-modal-next:first"]`);
}

async setTriggerAction(action: string, execution: string) {
await this.page.click(`xpath=//input[@id="add-trigger-form_action"]`);
await this.page.click(
`xpath=//div[@class="rc-virtual-list"]//div[contains(@class,"item-option") and @title="${action} ${execution}"]`
);
}

async setTestSelector(testSelector: {name: string}) {
if (testSelector.name) {
await this.page.click(`xpath=//div[@data-test="triggers-add-modal-action-switch"]//div[@title="BY NAME"]`);
await this.page.click(`xpath=//input[@id="add-trigger-form_testNameSelector"]`);
await this.page.click(
`xpath=//div[@class="rc-virtual-list"]//div[contains(@class,"option-content")]//span[text()="${testSelector.name}"]`
);
}
}

async clickCreateButton() {
await this.page.click(`xpath=//button[@data-test="webhooks-add-modal-next:second"]`);
}
}
74 changes: 74 additions & 0 deletions packages/e2e-tests/pages/CreateWebhookPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type {Page} from '@playwright/test';

import type {WebhookData} from '../types';

export class CreateWebhookPage {
public readonly page: Page;

public constructor(page: Page) {
this.page = page;
}

public async createWebhook(webhookData: WebhookData): Promise<void> {
await this.setBasicInput(webhookData.name, 'name');
await this.selectResourceIdentifier(webhookData.selector);
await this.selectTriggeredEvents(webhookData.events);

await this.clickNextButton();
await this.setBasicInput(webhookData.uri, 'uri');
await this.clickCreateWebhookButton();
}

async setBasicInput(value: string | number, inputName: string): Promise<void> {
await this.page.locator(`[id="webhook-creation-modal_${inputName}"]`).fill(`${value}`);
}

async clickNextButton() {
await this.page.click('//button[@data-test="webhooks-add-modal-next:first"]');
}

async clickCreateWebhookButton() {
await this.page.click('//button[@data-test="webhooks-add-modal-next:second"]');
}

async selectResourceIdentifier(resources: Record<string, string>): Promise<void> {
const multiSelectElementSelector = 'xpath=//div[@id="webhook-creation-modal_selector"]';
const multiSelectInputSelector = 'xpath=//div[@id="webhook-creation-modal_selector"]//input';

await this.selectLabels(resources, multiSelectElementSelector, multiSelectInputSelector);
}

async selectTriggeredEvents(events: string[]): Promise<void> {
const multiSelectElementSelector = 'xpath=//div[@id="webhook-creation-modal_events"]';

// eslint-disable-next-line no-restricted-syntax
for (const eventName of events) {
await this.selectMultiSelectValue(eventName, multiSelectElementSelector); // eslint-disable-line no-await-in-loop
}
}

async selectMultiSelectValue(value: string, multiSelectElement: string): Promise<void> {
await this.page.click(multiSelectElement);
await this.page.locator(`${multiSelectElement}//input`).fill(value);

await this.page.click(`${multiSelectElement}//div[contains(@class,"option") and text()="${value}"]`);
}

async selectLabels(
labels: Record<string, string>,
labelSelectElement: string,
labelSelectInputElement: string
): Promise<void> {
// eslint-disable-next-line no-restricted-syntax
for (const [name, value] of Object.entries(labels)) {
await this.selectCreateLabel(`${name}:${value}`, labelSelectElement, labelSelectInputElement); // eslint-disable-line no-await-in-loop
}
}

async selectCreateLabel(value: string, multiSelectElement: string, multiSelectInputSelector: string): Promise<void> {
await this.page.click(multiSelectElement);
await this.page.locator(multiSelectInputSelector).fill(value);

await this.page.keyboard.press('Enter');
}
}
17 changes: 17 additions & 0 deletions packages/e2e-tests/pages/TriggersPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {Page} from '@playwright/test';

export class TriggersPage {
public readonly page: Page;

public constructor(page: Page) {
this.page = page;
}

public async openCreateTriggerDialog(): Promise<void> {
await this.page.click('xpath=//button[@data-test="triggers-add-button"]');
}

public async openTriggerDetails(triggerName: string): Promise<void> {
await this.page.click(`xpath=//div[@data-test="triggers-list-item:${triggerName}"]`);
}
}
Loading

0 comments on commit 3894a79

Please sign in to comment.