diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 7a1e9a69..54d4e801 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -23,6 +23,7 @@ env: REDIS_HOST: localhost APP_ENV: production LICENSE_KEY: dummy_license_key + BACKEND_APP_URL: http://localhost:3000 jobs: test: diff --git a/packages/e2e-tests/fixtures/bullmq-helper.js b/packages/e2e-tests/fixtures/bullmq-helper.js new file mode 100644 index 00000000..d3ae96ed --- /dev/null +++ b/packages/e2e-tests/fixtures/bullmq-helper.js @@ -0,0 +1,11 @@ +const { expect } = require('../fixtures/index'); + +export const expectNoDelayedJobForFlow = async (flowId, request) => { + const token = btoa(`${process.env.BULLMQ_DASHBOARD_USERNAME}:${process.env.BULLMQ_DASHBOARD_PASSWORD}`); + const queues = await request.get(`${process.env.BACKEND_APP_URL}/admin/queues/api/queues?activeQueue=flow&status=delayed&page=1`, { + headers: {'Authorization': `Basic ${token}`} + }); + const queuesJsonResponse = await queues.json(); + const flowQueue = queuesJsonResponse.queues.find(queue => queue.name === "flow"); + await expect(flowQueue.jobs.find(job => job.name === `flow-${flowId}`)).toBeUndefined(); +}; diff --git a/packages/e2e-tests/fixtures/flow-editor-page.js b/packages/e2e-tests/fixtures/flow-editor-page.js index af321b59..63653cbf 100644 --- a/packages/e2e-tests/fixtures/flow-editor-page.js +++ b/packages/e2e-tests/fixtures/flow-editor-page.js @@ -24,6 +24,7 @@ export class FlowEditorPage extends AuthenticatedPage { this.unpublishFlowButton = this.page.getByTestId('unpublish-flow-button'); this.publishFlowButton = this.page.getByTestId('publish-flow-button'); this.infoSnackbar = this.page.getByTestId('flow-cannot-edit-info-snackbar'); + this.errorSnackbar = this.page.getByTestId('snackbar-error'); this.trigger = this.page.getByLabel('Trigger on weekends?'); this.stepCircularLoader = this.page.getByTestId('step-circular-loader'); this.flowName = this.page.getByTestId('editableTypography'); @@ -32,16 +33,12 @@ export class FlowEditorPage extends AuthenticatedPage { .locator('input'); this.flowStep = this.page.getByTestId('flow-step'); + this.rssFeedUrl = this.page.getByTestId('parameters.feedUrl-text'); } async createWebhookTrigger(workSynchronously) { - await this.appAutocomplete.click(); - await this.page.getByRole('option', { name: 'Webhook' }).click(); + this.chooseAppAndTrigger('Webhook', 'Catch raw webhook'); - await expect(this.eventAutocomplete).toBeVisible(); - await this.eventAutocomplete.click(); - await this.page.getByRole('option', { name: 'Catch raw webhook' }).click(); - await this.continueButton.click(); await this.page .getByTestId('parameters.workSynchronously-autocomplete') .click(); @@ -69,6 +66,19 @@ export class FlowEditorPage extends AuthenticatedPage { return await webhookUrl.inputValue(); } + async chooseAppAndTrigger(appName, triggerEvent) { + await expect(this.appAutocomplete).toHaveCount(1); + await this.appAutocomplete.click(); + await this.page.getByRole('option', { name: appName }).click(); + await expect(this.eventAutocomplete).toBeVisible(); + await this.eventAutocomplete.click(); + await Promise.all([ + this.page.waitForResponse(resp => /(apps\/.*\/triggers\/.*\/substeps)/.test(resp.url()) && resp.status() === 200), + this.page.getByRole('option', { name: triggerEvent }).click(), + ]); + await this.continueButton.click(); + } + async chooseAppAndEvent(appName, eventName) { await expect(this.appAutocomplete).toHaveCount(1); await this.appAutocomplete.click(); @@ -88,4 +98,10 @@ export class FlowEditorPage extends AuthenticatedPage { await expect(this.testOutput).toBeVisible(); await this.continueButton.click(); } + + async dismissErrorSnackbar() { + await expect(this.errorSnackbar).toBeVisible(); + await this.errorSnackbar.click(); + await expect(this.errorSnackbar).toHaveCount(0); + } } diff --git a/packages/e2e-tests/fixtures/postgres-config.js b/packages/e2e-tests/fixtures/postgres/postgres-config.js similarity index 100% rename from packages/e2e-tests/fixtures/postgres-config.js rename to packages/e2e-tests/fixtures/postgres/postgres-config.js diff --git a/packages/e2e-tests/fixtures/postgres/postgres-helper.js b/packages/e2e-tests/fixtures/postgres/postgres-helper.js new file mode 100644 index 00000000..dccf09a0 --- /dev/null +++ b/packages/e2e-tests/fixtures/postgres/postgres-helper.js @@ -0,0 +1,18 @@ +const { pgPool } = require('./postgres-config'); +const { expect } = require('../../fixtures/index'); + +export const flowShouldNotHavePublishedAtDateFilled = async (flowId) => { + const queryFlow = { + text: 'SELECT * FROM flows WHERE id = $1', + values: [flowId] + }; + + try { + const queryFlowResult = await pgPool.query(queryFlow); + expect(queryFlowResult.rowCount).toEqual(1); + expect(queryFlowResult.rows[0].published_at).toBeNull(); + } catch (err) { + console.error(err.message); + throw err; + } +}; diff --git a/packages/e2e-tests/tests/admin/applications.spec.js b/packages/e2e-tests/tests/admin/applications.spec.js index 2fad49b9..c1ede037 100644 --- a/packages/e2e-tests/tests/admin/applications.spec.js +++ b/packages/e2e-tests/tests/admin/applications.spec.js @@ -1,5 +1,5 @@ const { test, expect } = require('../../fixtures/index'); -const { pgPool } = require('../../fixtures/postgres-config'); +const { pgPool } = require('../../fixtures/postgres/postgres-config'); test.describe('Admin Applications', () => { test.beforeAll(async () => { diff --git a/packages/e2e-tests/tests/flow-editor/flow-validation.spec.js b/packages/e2e-tests/tests/flow-editor/flow-validation.spec.js new file mode 100644 index 00000000..6de29de3 --- /dev/null +++ b/packages/e2e-tests/tests/flow-editor/flow-validation.spec.js @@ -0,0 +1,53 @@ +const { test, expect } = require('../../fixtures/index'); +const { expectNoDelayedJobForFlow } = require('../../fixtures/bullmq-helper'); +const { flowShouldNotHavePublishedAtDateFilled } = require('../../fixtures/postgres/postgres-helper'); + +test.describe('Flow Validation', () => { + test.beforeEach(async ({ page }) => { + await page.getByTestId('create-flow-button').click(); + await page.waitForURL( + /\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/ + ); + await expect(page.getByTestId('flow-step')).toHaveCount(2); + }); + + test('Should not be able to publish flow without trigger', async ({ + flowEditorPage, + page, + request + }) => { + const flowId = await page.url().split('editor/').pop(); + + await flowEditorPage.flowName.click(); + await flowEditorPage.flowNameInput.fill('incompleteFlow'); + await flowEditorPage.chooseAppAndTrigger('RSS', 'New items in feed'); + + await flowEditorPage.publishFlowButton.click(); + await flowEditorPage.dismissErrorSnackbar(); + + await flowShouldNotHavePublishedAtDateFilled(flowId); + await expectNoDelayedJobForFlow(flowId, request); + + await flowEditorPage.rssFeedUrl.fill('http://rss.cnn.com/rss/money_mostpopular.rss'); + await expect(flowEditorPage.continueButton).toHaveCount(1); + await flowEditorPage.continueButton.click(); + + await flowEditorPage.publishFlowButton.click(); + await flowEditorPage.dismissErrorSnackbar(); + + await flowShouldNotHavePublishedAtDateFilled(flowId); + await expectNoDelayedJobForFlow(flowId, request); + + await expect(flowEditorPage.testOutput).not.toBeVisible(); + await flowEditorPage.testAndContinueButton.click(); + await expect(flowEditorPage.testOutput).toBeVisible(); + await expect(flowEditorPage.hasNoOutput).not.toBeVisible(); + await flowEditorPage.continueButton.click(); + + await flowEditorPage.publishFlowButton.click(); + await expect(page.getByTestId('snackbar-error')).toBeVisible(); + + await flowShouldNotHavePublishedAtDateFilled(flowId); + await expectNoDelayedJobForFlow(flowId, request); + }); +}); diff --git a/packages/e2e-tests/tests/user-invitation/invitation.spec.js b/packages/e2e-tests/tests/user-invitation/invitation.spec.js index 2035f808..8455044d 100644 --- a/packages/e2e-tests/tests/user-invitation/invitation.spec.js +++ b/packages/e2e-tests/tests/user-invitation/invitation.spec.js @@ -1,5 +1,5 @@ const { publicTest, expect } = require('../../fixtures/index'); -const { pgPool } = require('../../fixtures/postgres-config'); +const { pgPool } = require('../../fixtures/postgres/postgres-config'); const { DateTime } = require('luxon'); publicTest.describe('Accept invitation page', () => {