Compare commits
5 Commits
AUT-1025
...
migrate-fl
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9752e2c4d2 | ||
![]() |
a5b31da3cc | ||
![]() |
8f7785e9d2 | ||
![]() |
69297c2dd8 | ||
![]() |
1c8e9fac7c |
25
.github/workflows/playwright.yml
vendored
Normal file
25
.github/workflows/playwright.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: Automatisch UI Test
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 12 * * *'
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: yarn playwright install --with-deps
|
||||||
|
- name: Run Playwright tests
|
||||||
|
run: yarn playwright test
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: playwright-report/
|
||||||
|
retention-days: 30
|
5
packages/e2e-tests/.gitignore
vendored
Normal file
5
packages/e2e-tests/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
/output
|
12
packages/e2e-tests/fixtures/applications-page.js
Normal file
12
packages/e2e-tests/fixtures/applications-page.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
const { BasePage } = require('./base-page');
|
||||||
|
|
||||||
|
export class ApplicationsPage extends BasePage {
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
const { path: plainPath, ...restOptions } = options;
|
||||||
|
|
||||||
|
const computedPath = path.join('applications', plainPath);
|
||||||
|
|
||||||
|
return await super.screenshot({ path: computedPath, ...restOptions });
|
||||||
|
}
|
||||||
|
}
|
34
packages/e2e-tests/fixtures/base-page.js
Normal file
34
packages/e2e-tests/fixtures/base-page.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
export class BasePage {
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickAway() {
|
||||||
|
await this.page.locator('body').click({ position: { x: 0, y: 0 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
const { path: plainPath, ...restOptions } = options;
|
||||||
|
|
||||||
|
const computedPath = path.join('output/screenshots', plainPath);
|
||||||
|
|
||||||
|
return await this.page.screenshot({ path: computedPath, ...restOptions });
|
||||||
|
}
|
||||||
|
|
||||||
|
async login() {
|
||||||
|
await this.page.goto('/login');
|
||||||
|
await this.page
|
||||||
|
.getByTestId('email-text-field')
|
||||||
|
.fill(process.env.LOGIN_EMAIL);
|
||||||
|
await this.page
|
||||||
|
.getByTestId('password-text-field')
|
||||||
|
.fill(process.env.LOGIN_PASSWORD);
|
||||||
|
|
||||||
|
await this.page.getByTestId('login-button').click();
|
||||||
|
}
|
||||||
|
}
|
16
packages/e2e-tests/fixtures/connections-page.js
Normal file
16
packages/e2e-tests/fixtures/connections-page.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
const { BasePage } = require('./base-page');
|
||||||
|
|
||||||
|
export class ConnectionsPage extends BasePage {
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
const { path: plainPath, ...restOptions } = options;
|
||||||
|
|
||||||
|
const computedPath = path.join('connections', plainPath);
|
||||||
|
|
||||||
|
return await super.screenshot({ path: computedPath, ...restOptions });
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickAddConnectionButton() {
|
||||||
|
await this.page.getByTestId('add-connection-button').click();
|
||||||
|
}
|
||||||
|
}
|
12
packages/e2e-tests/fixtures/executions-page.js
Normal file
12
packages/e2e-tests/fixtures/executions-page.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
const { BasePage } = require('./base-page');
|
||||||
|
|
||||||
|
export class ExecutionsPage extends BasePage {
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
const { path: plainPath, ...restOptions } = options;
|
||||||
|
|
||||||
|
const computedPath = path.join('executions', plainPath);
|
||||||
|
|
||||||
|
return await super.screenshot({ path: computedPath, ...restOptions });
|
||||||
|
}
|
||||||
|
}
|
27
packages/e2e-tests/fixtures/flow-editor-page.js
Normal file
27
packages/e2e-tests/fixtures/flow-editor-page.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
const path = require('node:path');
|
||||||
|
const { BasePage } = require('./base-page');
|
||||||
|
|
||||||
|
export class FlowEditorPage extends BasePage {
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
this.appAutocomplete = this.page.getByTestId('choose-app-autocomplete');
|
||||||
|
this.eventAutocomplete = this.page.getByTestId('choose-event-autocomplete');
|
||||||
|
this.continueButton = this.page.getByTestId('flow-substep-continue-button');
|
||||||
|
this.connectionAutocomplete = this.page.getByTestId(
|
||||||
|
'choose-connection-autocomplete'
|
||||||
|
);
|
||||||
|
this.testOuput = this.page.getByTestId('flow-test-substep-output');
|
||||||
|
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.trigger = this.page.getByLabel('Trigger on weekends?');
|
||||||
|
}
|
||||||
|
|
||||||
|
async screenshot(options = {}) {
|
||||||
|
const { path: plainPath, ...restOptions } = options;
|
||||||
|
|
||||||
|
const computedPath = path.join('flow-editor', plainPath);
|
||||||
|
|
||||||
|
return await super.screenshot({ path: computedPath, ...restOptions });
|
||||||
|
}
|
||||||
|
}
|
21
packages/e2e-tests/fixtures/index.js
Normal file
21
packages/e2e-tests/fixtures/index.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const base = require('@playwright/test');
|
||||||
|
const { ApplicationsPage } = require('./applications-page');
|
||||||
|
const { ConnectionsPage } = require('./connections-page');
|
||||||
|
const { ExecutionsPage } = require('./executions-page');
|
||||||
|
const { FlowEditorPage } = require('./flow-editor-page');
|
||||||
|
|
||||||
|
exports.test = base.test.extend({
|
||||||
|
applicationsPage: async ({ page }, use) => {
|
||||||
|
await use(new ApplicationsPage(page));
|
||||||
|
},
|
||||||
|
connectionsPage: async ({ page }, use) => {
|
||||||
|
await use(new ConnectionsPage(page));
|
||||||
|
},
|
||||||
|
executionsPage: async ({ page }, use) => {
|
||||||
|
await use(new ExecutionsPage(page));
|
||||||
|
},
|
||||||
|
flowEditorPage: async ({ page }, use) => {
|
||||||
|
await use(new FlowEditorPage(page));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
exports.expect = base.expect;
|
@@ -5,7 +5,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"open": "cypress open"
|
"open": "cypress open",
|
||||||
|
"playwright": "playwright test"
|
||||||
},
|
},
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,10 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.36.2",
|
||||||
"cypress": "^10.9.0"
|
"cypress": "^10.9.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^16.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
82
packages/e2e-tests/playwright.config.js
Normal file
82
packages/e2e-tests/playwright.config.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { defineConfig, devices } = require('@playwright/test');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://playwright.dev/docs/test-configuration
|
||||||
|
*/
|
||||||
|
module.exports = defineConfig({
|
||||||
|
testDir: './tests',
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: true,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: process.env.CI ? 'github' : 'html',
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
baseURL: process.env.CI
|
||||||
|
? 'https://sandbox.automatisch.io'
|
||||||
|
: 'http://localhost:3001',
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
testIdAttribute: 'data-test',
|
||||||
|
viewport: { width: 1280, height: 720 },
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: 'firefox',
|
||||||
|
// use: { ...devices['Desktop Firefox'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: 'webkit',
|
||||||
|
// use: { ...devices['Desktop Safari'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
// webServer: {
|
||||||
|
// command: 'npm run start',
|
||||||
|
// url: 'http://127.0.0.1:3000',
|
||||||
|
// reuseExistingServer: !process.env.CI,
|
||||||
|
// },
|
||||||
|
});
|
67
packages/e2e-tests/tests/apps/list-apps.spec.js
Normal file
67
packages/e2e-tests/tests/apps/list-apps.spec.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
|
test.describe('Apps page', () => {
|
||||||
|
test.beforeEach(async ({ page, applicationsPage }) => {
|
||||||
|
await applicationsPage.login();
|
||||||
|
await page.getByTestId('apps-page-drawer-link').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays applications', async ({ page, applicationsPage }) => {
|
||||||
|
await page.getByTestId('apps-loader').waitFor({
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
await expect(page.getByTestId('app-row')).not.toHaveCount(0);
|
||||||
|
|
||||||
|
await applicationsPage.screenshot({
|
||||||
|
path: 'Applications.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('can add connection', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await expect(page.getByTestId('add-connection-button')).toBeVisible();
|
||||||
|
await page.getByTestId('add-connection-button').click();
|
||||||
|
await page
|
||||||
|
.getByTestId('search-for-app-loader')
|
||||||
|
.waitFor({ state: 'detached' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lists applications', async ({ page, applicationsPage }) => {
|
||||||
|
const appListItemCount = await page.getByTestId('app-list-item').count();
|
||||||
|
expect(appListItemCount).toBeGreaterThan(10);
|
||||||
|
|
||||||
|
await applicationsPage.clickAway();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('searches an application', async ({ page, applicationsPage }) => {
|
||||||
|
await page.getByTestId('search-for-app-text-field').fill('DeepL');
|
||||||
|
await expect(page.getByTestId('app-list-item')).toHaveCount(1);
|
||||||
|
|
||||||
|
await applicationsPage.clickAway();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('goes to app page to create a connection', async ({
|
||||||
|
page,
|
||||||
|
applicationsPage,
|
||||||
|
}) => {
|
||||||
|
await page.getByTestId('app-list-item').first().click();
|
||||||
|
await expect(page).toHaveURL('/app/deepl/connections/add');
|
||||||
|
await expect(page.getByTestId('add-app-connection-dialog')).toBeVisible();
|
||||||
|
|
||||||
|
await applicationsPage.clickAway();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('closes the dialog on backdrop click', async ({
|
||||||
|
page,
|
||||||
|
applicationsPage,
|
||||||
|
}) => {
|
||||||
|
await page.getByTestId('app-list-item').first().click();
|
||||||
|
await expect(page).toHaveURL('/app/deepl/connections/add');
|
||||||
|
await expect(page.getByTestId('add-app-connection-dialog')).toBeVisible();
|
||||||
|
await applicationsPage.clickAway();
|
||||||
|
await expect(page).toHaveURL('/app/deepl/connections');
|
||||||
|
await expect(page.getByTestId('add-app-connection-dialog')).toBeHidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,50 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
|
test.describe('Connections page', () => {
|
||||||
|
test.beforeEach(async ({ page, connectionsPage }) => {
|
||||||
|
await connectionsPage.login();
|
||||||
|
await page.getByTestId('apps-page-drawer-link').click();
|
||||||
|
await page.goto('/app/ntfy/connections');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows connections if any', async ({ page, connectionsPage }) => {
|
||||||
|
await page.getByTestId('apps-loader').waitFor({
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
|
||||||
|
await connectionsPage.screenshot({
|
||||||
|
path: 'Connections.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('can add connection', () => {
|
||||||
|
test('has a button to open add connection dialog', async ({ page }) => {
|
||||||
|
await expect(page.getByTestId('add-connection-button')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add connection button takes user to add connection page', async ({
|
||||||
|
page,
|
||||||
|
connectionsPage,
|
||||||
|
}) => {
|
||||||
|
await connectionsPage.clickAddConnectionButton();
|
||||||
|
await expect(page).toHaveURL('/app/ntfy/connections/add');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows add connection dialog to create a new connection', async ({
|
||||||
|
page,
|
||||||
|
connectionsPage,
|
||||||
|
}) => {
|
||||||
|
await connectionsPage.clickAddConnectionButton();
|
||||||
|
await expect(page).toHaveURL('/app/ntfy/connections/add');
|
||||||
|
await page.getByTestId('create-connection-button').click();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('create-connection-button')
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
await connectionsPage.screenshot({
|
||||||
|
path: 'Ntfy connections after creating a connection.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,39 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
|
test.describe('Executions page', () => {
|
||||||
|
test.beforeEach(async ({ page, executionsPage }) => {
|
||||||
|
await executionsPage.login();
|
||||||
|
|
||||||
|
await page.getByTestId('executions-page-drawer-link').click();
|
||||||
|
await page.getByTestId('execution-row').first().click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/executions\//);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays data in by default', async ({ page, executionsPage }) => {
|
||||||
|
await expect(page.getByTestId('execution-step').last()).toBeVisible();
|
||||||
|
await expect(page.getByTestId('execution-step')).toHaveCount(2);
|
||||||
|
|
||||||
|
await executionsPage.screenshot({
|
||||||
|
path: 'Execution - data in.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays data out', async ({ page, executionsPage }) => {
|
||||||
|
const executionStepCount = await page.getByTestId('execution-step').count();
|
||||||
|
for (let i = 0; i < executionStepCount; i++) {
|
||||||
|
await page.getByTestId('data-out-tab').nth(i).click();
|
||||||
|
await expect(page.getByTestId('data-out-panel').nth(i)).toBeVisible();
|
||||||
|
|
||||||
|
await executionsPage.screenshot({
|
||||||
|
path: `Execution - data out - ${i}.png`,
|
||||||
|
animations: 'disabled',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not display error', async ({ page }) => {
|
||||||
|
await expect(page.getByTestId('error-tab')).toBeHidden();
|
||||||
|
});
|
||||||
|
});
|
19
packages/e2e-tests/tests/executions/list-executions.spec.js
Normal file
19
packages/e2e-tests/tests/executions/list-executions.spec.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
|
test.describe('Executions page', () => {
|
||||||
|
test.beforeEach(async ({ page, executionsPage }) => {
|
||||||
|
await executionsPage.login();
|
||||||
|
|
||||||
|
await page.getByTestId('executions-page-drawer-link').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays executions', async ({ page, executionsPage }) => {
|
||||||
|
await page.getByTestId('executions-loader').waitFor({
|
||||||
|
state: 'detached',
|
||||||
|
});
|
||||||
|
await expect(page.getByTestId('execution-row').first()).toBeVisible();
|
||||||
|
|
||||||
|
await executionsPage.screenshot({ path: 'Executions.png' });
|
||||||
|
});
|
||||||
|
});
|
205
packages/e2e-tests/tests/flow-editor/create-flow.spec.js
Normal file
205
packages/e2e-tests/tests/flow-editor/create-flow.spec.js
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { FlowEditorPage } = require('../../fixtures/flow-editor-page');
|
||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
|
test.describe.configure({ mode: 'serial' });
|
||||||
|
|
||||||
|
let page;
|
||||||
|
let flowEditorPage;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
page = await browser.newPage();
|
||||||
|
flowEditorPage = new FlowEditorPage(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create flow', async ({}) => {
|
||||||
|
await flowEditorPage.login();
|
||||||
|
|
||||||
|
await flowEditorPage.page.getByTestId('create-flow-button').click();
|
||||||
|
await expect(flowEditorPage.page).toHaveURL(/\/editor\/create/);
|
||||||
|
await expect(flowEditorPage.page).toHaveURL(
|
||||||
|
/\/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}/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has two steps by default', async ({}) => {
|
||||||
|
await expect(flowEditorPage.page.getByTestId('flow-step')).toHaveCount(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('arrange Scheduler trigger', () => {
|
||||||
|
test.describe('choose app and event substep', () => {
|
||||||
|
test('choose application', async ({}) => {
|
||||||
|
await flowEditorPage.appAutocomplete.click();
|
||||||
|
await flowEditorPage.page
|
||||||
|
.getByRole('option', { name: 'Scheduler' })
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('choose an event', async ({}) => {
|
||||||
|
await expect(flowEditorPage.eventAutocomplete).toBeVisible();
|
||||||
|
await flowEditorPage.eventAutocomplete.click();
|
||||||
|
await flowEditorPage.page
|
||||||
|
.getByRole('option', { name: 'Every hour' })
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('continue to next step', async ({}) => {
|
||||||
|
await flowEditorPage.continueButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('collapses the substep', async ({}) => {
|
||||||
|
await expect(flowEditorPage.appAutocomplete).not.toBeVisible();
|
||||||
|
await expect(flowEditorPage.eventAutocomplete).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('set up a trigger', () => {
|
||||||
|
test('choose "yes" in "trigger on weekends?"', async ({}) => {
|
||||||
|
await expect(flowEditorPage.trigger).toBeVisible();
|
||||||
|
await flowEditorPage.trigger.click();
|
||||||
|
await flowEditorPage.page.getByRole('option', { name: 'Yes' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('continue to next step', async ({}) => {
|
||||||
|
await flowEditorPage.continueButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('collapses the substep', async ({}) => {
|
||||||
|
await expect(flowEditorPage.trigger).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('test trigger', () => {
|
||||||
|
test('show sample output', async ({}) => {
|
||||||
|
await expect(flowEditorPage.testOuput).not.toBeVisible();
|
||||||
|
await flowEditorPage.continueButton.click();
|
||||||
|
await expect(flowEditorPage.testOuput).toBeVisible();
|
||||||
|
await flowEditorPage.screenshot({
|
||||||
|
path: 'Scheduler trigger test output.png',
|
||||||
|
});
|
||||||
|
await flowEditorPage.continueButton.click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('arrange Ntfy action', () => {
|
||||||
|
test.describe('choose app and event substep', () => {
|
||||||
|
test('choose application', async ({}) => {
|
||||||
|
await flowEditorPage.appAutocomplete.click();
|
||||||
|
await flowEditorPage.page.getByRole('option', { name: 'Ntfy' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('choose an event', async ({}) => {
|
||||||
|
await expect(flowEditorPage.eventAutocomplete).toBeVisible();
|
||||||
|
await flowEditorPage.eventAutocomplete.click();
|
||||||
|
await flowEditorPage.page
|
||||||
|
.getByRole('option', { name: 'Send message' })
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('continue to next step', async ({}) => {
|
||||||
|
await flowEditorPage.continueButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('collapses the substep', async ({}) => {
|
||||||
|
await expect(flowEditorPage.appAutocomplete).not.toBeVisible();
|
||||||
|
await expect(flowEditorPage.eventAutocomplete).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('choose connection', () => {
|
||||||
|
test('choose connection list item', async ({}) => {
|
||||||
|
await flowEditorPage.connectionAutocomplete.click();
|
||||||
|
await flowEditorPage.page.getByRole('listitem').first().click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('continue to next step', async ({}) => {
|
||||||
|
await flowEditorPage.continueButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('collapses the substep', async ({}) => {
|
||||||
|
await expect(flowEditorPage.connectionAutocomplete).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('set up action', () => {
|
||||||
|
test('fill topic and message body', async ({}) => {
|
||||||
|
await flowEditorPage.page
|
||||||
|
.getByTestId('parameters.topic-power-input')
|
||||||
|
.locator('[contenteditable]')
|
||||||
|
.fill('Topic');
|
||||||
|
await flowEditorPage.page
|
||||||
|
.getByTestId('parameters.message-power-input')
|
||||||
|
.locator('[contenteditable]')
|
||||||
|
.fill('Message body');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('continue to next step', async ({}) => {
|
||||||
|
await flowEditorPage.continueButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('collapses the substep', async ({}) => {
|
||||||
|
await expect(flowEditorPage.connectionAutocomplete).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('test trigger', () => {
|
||||||
|
test('show sample output', async ({}) => {
|
||||||
|
await expect(flowEditorPage.testOuput).not.toBeVisible();
|
||||||
|
await flowEditorPage.page
|
||||||
|
.getByTestId('flow-substep-continue-button')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
await expect(flowEditorPage.testOuput).toBeVisible();
|
||||||
|
await flowEditorPage.screenshot({
|
||||||
|
path: 'Ntfy action test output.png',
|
||||||
|
});
|
||||||
|
await flowEditorPage.continueButton.click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('publish and unpublish', () => {
|
||||||
|
test('publish flow', async ({}) => {
|
||||||
|
await expect(flowEditorPage.unpublishFlowButton).not.toBeVisible();
|
||||||
|
await expect(flowEditorPage.publishFlowButton).toBeVisible();
|
||||||
|
await flowEditorPage.publishFlowButton.click();
|
||||||
|
await expect(flowEditorPage.publishFlowButton).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows read-only sticky snackbar', async ({}) => {
|
||||||
|
await expect(flowEditorPage.infoSnackbar).toBeVisible();
|
||||||
|
await flowEditorPage.screenshot({
|
||||||
|
path: 'Published flow.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unpublish from snackbar', async ({}) => {
|
||||||
|
await flowEditorPage.page
|
||||||
|
.getByTestId('unpublish-flow-from-snackbar')
|
||||||
|
.click();
|
||||||
|
await expect(flowEditorPage.infoSnackbar).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('publish once again', async ({}) => {
|
||||||
|
await expect(flowEditorPage.publishFlowButton).toBeVisible();
|
||||||
|
await flowEditorPage.publishFlowButton.click();
|
||||||
|
await expect(flowEditorPage.publishFlowButton).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unpublish from layout top bar', async ({}) => {
|
||||||
|
await expect(flowEditorPage.unpublishFlowButton).toBeVisible();
|
||||||
|
await flowEditorPage.unpublishFlowButton.click();
|
||||||
|
await expect(flowEditorPage.unpublishFlowButton).not.toBeVisible();
|
||||||
|
await flowEditorPage.screenshot({
|
||||||
|
path: 'Unpublished flow.png',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('in layout', () => {
|
||||||
|
test('can go back to flows page', async ({}) => {
|
||||||
|
await flowEditorPage.page.getByTestId('editor-go-back-button').click();
|
||||||
|
await expect(flowEditorPage.page).toHaveURL('/flows');
|
||||||
|
});
|
||||||
|
});
|
@@ -101,7 +101,9 @@ export default function AddNewAppConnection(
|
|||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
}
|
||||||
label={formatMessage('apps.searchApp')}
|
label={formatMessage('apps.searchApp')}
|
||||||
data-test="search-for-app-text-field"
|
inputProps={{
|
||||||
|
'data-test': 'search-for-app-text-field',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -109,7 +111,10 @@ export default function AddNewAppConnection(
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<List sx={{ pt: 2, width: '100%' }}>
|
<List sx={{ pt: 2, width: '100%' }}>
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
<CircularProgress
|
||||||
|
data-test="search-for-app-loader"
|
||||||
|
sx={{ display: 'block', margin: '20px auto' }}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
|
@@ -37,10 +37,12 @@ function ExecutionStepDate(props: Pick<IExecutionStep, 'createdAt'>) {
|
|||||||
const relativeCreatedAt = createdAt.toRelative();
|
const relativeCreatedAt = createdAt.toRelative();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={createdAt.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}>
|
<Tooltip
|
||||||
|
title={createdAt.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
|
||||||
|
>
|
||||||
<Typography variant="caption" gutterBottom>
|
<Typography variant="caption" gutterBottom>
|
||||||
{formatMessage('executionStep.executedAt', {
|
{formatMessage('executionStep.executedAt', {
|
||||||
datetime: relativeCreatedAt
|
datetime: relativeCreatedAt,
|
||||||
})}
|
})}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -117,7 +119,7 @@ export default function ExecutionStep(
|
|||||||
<SearchableJSONViewer data={executionStep.dataIn} />
|
<SearchableJSONViewer data={executionStep.dataIn} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value={activeTabIndex} index={1}>
|
<TabPanel value={activeTabIndex} index={1} data-test="data-out-panel">
|
||||||
<SearchableJSONViewer data={executionStep.dataOut} />
|
<SearchableJSONViewer data={executionStep.dataOut} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
|
@@ -60,11 +60,13 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
const [showVariableSuggestions, setShowVariableSuggestions] =
|
const [showVariableSuggestions, setShowVariableSuggestions] =
|
||||||
React.useState(false);
|
React.useState(false);
|
||||||
|
|
||||||
const disappearSuggestionsOnShift = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
const disappearSuggestionsOnShift = (
|
||||||
|
event: React.KeyboardEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
if (event.code === 'Tab') {
|
if (event.code === 'Tab') {
|
||||||
setShowVariableSuggestions(false);
|
setShowVariableSuggestions(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const stepsWithVariables = React.useMemo(() => {
|
const stepsWithVariables = React.useMemo(() => {
|
||||||
return processStepWithExecutions(priorStepsWithExecutions);
|
return processStepWithExecutions(priorStepsWithExecutions);
|
||||||
@@ -112,7 +114,10 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* ref-able single child for ClickAwayListener */}
|
{/* ref-able single child for ClickAwayListener */}
|
||||||
<ChildrenWrapper style={{ width: '100%' }} data-test="power-input">
|
<ChildrenWrapper
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
data-test={`${name}-power-input`}
|
||||||
|
>
|
||||||
<FakeInput disabled={disabled}>
|
<FakeInput disabled={disabled}>
|
||||||
<InputLabelWrapper>
|
<InputLabelWrapper>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
@@ -140,7 +145,10 @@ const PowerInput = (props: PowerInputProps) => {
|
|||||||
/>
|
/>
|
||||||
</FakeInput>
|
</FakeInput>
|
||||||
{/* ghost placer for the variables popover */}
|
{/* ghost placer for the variables popover */}
|
||||||
<div ref={editorRef} style={{ position: 'absolute', right: 16, left: 16 }} />
|
<div
|
||||||
|
ref={editorRef}
|
||||||
|
style={{ position: 'absolute', right: 16, left: 16 }}
|
||||||
|
/>
|
||||||
|
|
||||||
<Popper
|
<Popper
|
||||||
open={showVariableSuggestions}
|
open={showVariableSuggestions}
|
||||||
|
@@ -14,6 +14,7 @@ type TextFieldProps = {
|
|||||||
name: string;
|
name: string;
|
||||||
clickToCopy?: boolean;
|
clickToCopy?: boolean;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
'data-test'?: string;
|
||||||
} & MuiTextFieldProps;
|
} & MuiTextFieldProps;
|
||||||
|
|
||||||
const createCopyAdornment = (
|
const createCopyAdornment = (
|
||||||
@@ -44,6 +45,7 @@ export default function TextField(props: TextFieldProps): React.ReactElement {
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
onBlur,
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
|
'data-test': dataTest,
|
||||||
...textFieldProps
|
...textFieldProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -82,6 +84,9 @@ export default function TextField(props: TextFieldProps): React.ReactElement {
|
|||||||
readOnly,
|
readOnly,
|
||||||
endAdornment: clickToCopy ? createCopyAdornment(inputRef) : null,
|
endAdornment: clickToCopy ? createCopyAdornment(inputRef) : null,
|
||||||
}}
|
}}
|
||||||
|
inputProps={{
|
||||||
|
'data-test': dataTest,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
22
yarn.lock
22
yarn.lock
@@ -3380,6 +3380,16 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@octokit/openapi-types" "^11.2.0"
|
"@octokit/openapi-types" "^11.2.0"
|
||||||
|
|
||||||
|
"@playwright/test@^1.36.2":
|
||||||
|
version "1.36.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.36.2.tgz#9edd68a02b0929c5d78d9479a654ceb981dfb592"
|
||||||
|
integrity sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
playwright-core "1.36.2"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "2.3.2"
|
||||||
|
|
||||||
"@pmmmwh/react-refresh-webpack-plugin@^0.5.3":
|
"@pmmmwh/react-refresh-webpack-plugin@^0.5.3":
|
||||||
version "0.5.4"
|
version "0.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.4.tgz#df0d0d855fc527db48aac93c218a0bf4ada41f99"
|
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.4.tgz#df0d0d855fc527db48aac93c218a0bf4ada41f99"
|
||||||
@@ -7961,6 +7971,11 @@ dotenv@^10.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||||
|
|
||||||
|
dotenv@^16.3.1:
|
||||||
|
version "16.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||||
|
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
||||||
|
|
||||||
duplexer3@^0.1.4:
|
duplexer3@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||||
@@ -9400,7 +9415,7 @@ fs.realpath@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||||
|
|
||||||
fsevents@^2.3.2, fsevents@~2.3.2:
|
fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||||
@@ -13928,6 +13943,11 @@ pkg-up@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up "^3.0.0"
|
find-up "^3.0.0"
|
||||||
|
|
||||||
|
playwright-core@1.36.2:
|
||||||
|
version "1.36.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.36.2.tgz#32382f2d96764c24c65a86ea336cf79721c2e50e"
|
||||||
|
integrity sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==
|
||||||
|
|
||||||
plur@^4.0.0:
|
plur@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84"
|
resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84"
|
||||||
|
Reference in New Issue
Block a user