This is an automated email from the ASF dual-hosted git repository.

zehnder pushed a commit to branch STREAMPIPES-380
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 52ec1ee852b9e11fdf991b6804c9f22b52cd0a1c
Author: Philipp Zehnder <[email protected]>
AuthorDate: Tue Jun 22 14:35:18 2021 +0200

    [STREAMPIPES-380] Initial commit with new e2e test framework
---
 ui/cypress.json                                    |   9 ++
 ui/cypress/README.md                               |  49 ++++++
 ui/cypress/fixtures/fileTest/random.csv            |  11 ++
 ui/cypress/fixtures/fileTest/test.csv              |   4 +
 .../pipelineElement/fieldRenamer1/expected.csv     |  11 ++
 .../pipelineElement/fieldRenamer1/input.csv        |  11 ++
 .../pipelineElement/numericalFilter1/expected.csv  |   6 +
 .../pipelineElement/numericalFilter1/input.csv     |  11 ++
 ui/cypress/integration/adapter/fileStream.ts       |  27 ++++
 .../integration/adapter/machineDataSimulator.ts    |  14 ++
 .../adapter/randomDataSimulatorStream.spec.ts      |  15 ++
 .../integration/dataexplorer/testDataExplorer.ts   |  11 ++
 .../fileManagement/testFileManagement.ts           |  16 ++
 ui/cypress/integration/pipeline/pipelineTest.ts    |  32 ++++
 .../pipelineElement/fieldRenamerTest1.ts           |  21 +++
 .../pipelineElement/numericalFilterTest1.ts        |  24 +++
 ui/cypress/plugins/index.ts                        |  16 ++
 .../support/builder/GenericAdapterBuilder.ts       |  59 +++++++
 ui/cypress/support/builder/PipelineBuilder.ts      |  39 +++++
 .../support/builder/PipelineElementBuilder.ts      |  32 ++++
 .../support/builder/SpecificAdapterBuilder.ts      |  43 +++++
 ui/cypress/support/general/dataCy.ts               |  14 ++
 ui/cypress/support/general/login.ts                |  22 +++
 ui/cypress/support/general/logout.ts               |  20 +++
 ui/cypress/support/index.ts                        |  11 ++
 ui/cypress/support/model/AdapterInput.ts           |   5 +
 ui/cypress/support/model/GenericAdapterInput.ts    |   8 +
 ui/cypress/support/model/PipelineElementInput.ts   |   6 +
 ui/cypress/support/model/PipelineInput.ts          |  10 ++
 ui/cypress/support/model/SpecificAdapterInput.ts   |   6 +
 ui/cypress/support/model/UserInput.ts              |   5 +
 ui/cypress/support/utils/AdapterUtils.ts           | 179 +++++++++++++++++++++
 ui/cypress/support/utils/DataLakeUtils.ts          |  34 ++++
 ui/cypress/support/utils/FileManagementUtils.ts    |  33 ++++
 ui/cypress/support/utils/PipelineUtils.ts          |  88 ++++++++++
 .../support/utils/ProcessingElementTestUtils.ts    |  58 +++++++
 ui/cypress/support/utils/StaticPropertyUtils.ts    |  21 +++
 ui/cypress/tsconfig.json                           |  11 ++
 ui/package.json                                    |   6 +-
 39 files changed, 997 insertions(+), 1 deletion(-)

diff --git a/ui/cypress.json b/ui/cypress.json
new file mode 100644
index 0000000..9439c4e
--- /dev/null
+++ b/ui/cypress.json
@@ -0,0 +1,9 @@
+{
+  "projectId": "z5n3e9",
+  "env": {
+    "TAKE_SCREENSHOT": "false"
+  },
+  "baseUrl": "http://localhost:8082";,
+  "trashAssetsBeforeRuns": false,
+  "videoCompression": false
+}
diff --git a/ui/cypress/README.md b/ui/cypress/README.md
new file mode 100644
index 0000000..b598d34
--- /dev/null
+++ b/ui/cypress/README.md
@@ -0,0 +1,49 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+  
+# End2End tests with cypress
+This folder contains a WIP framework for automated E2E tests of StreamPipes.
+
+## How to run the tests?
+1. Set up and install a clean StreamPipes instance.
+2. Start cypress:
+```bash
+npm run test-e2e
+```
+
+**User**: [email protected] **Password**: test1234
+
+>**Note:** This can be changed in **support/general/login.ts**
+
+
+>**Note:** The base URL can be configured in **cypress.json**
+
+## Design guidlines
+* Each test sets up its own test environment (e.g. upload files)
+* Once the test is performed all configurations and system changes should be 
removed (e.g. delete uploaded files)
+* There should not be any dependencies between tests
+
+## Directories
+* **fixtures**: 
+    * Files that are required for tests
+* **integrations**: 
+    * Contains the actual tests
+* **plugins**: 
+    * Cypress plugins can be added here
+* **support**: 
+    * Contains code for the StreamPipes test framework (e.g. model, utils 
functions, ...)
diff --git a/ui/cypress/fixtures/fileTest/random.csv 
b/ui/cypress/fixtures/fileTest/random.csv
new file mode 100644
index 0000000..2420ef5
--- /dev/null
+++ b/ui/cypress/fixtures/fileTest/random.csv
@@ -0,0 +1,11 @@
+timestamp;count;randomnumber;randomtext
+1623871499055;122.0;62.0;c
+1623871500059;123.0;46.0;a
+1623871501064;124.0;41.0;b
+1623871502070;125.0;41.0;b
+1623871503078;126.0;22.0;b
+1623871504082;127.0;56.0;a
+1623871505084;128.0;95.0;b
+1623871506086;129.0;77.0;a
+1623871507091;130.0;85.0;b
+1623871508093;131.0;22.0;a
diff --git a/ui/cypress/fixtures/fileTest/test.csv 
b/ui/cypress/fixtures/fileTest/test.csv
new file mode 100644
index 0000000..9374177
--- /dev/null
+++ b/ui/cypress/fixtures/fileTest/test.csv
@@ -0,0 +1,4 @@
+one,two
+1,2
+3,5
+4,6
diff --git a/ui/cypress/fixtures/pipelineElement/fieldRenamer1/expected.csv 
b/ui/cypress/fixtures/pipelineElement/fieldRenamer1/expected.csv
new file mode 100644
index 0000000..c977de6
--- /dev/null
+++ b/ui/cypress/fixtures/pipelineElement/fieldRenamer1/expected.csv
@@ -0,0 +1,11 @@
+time;newname;randomnumber;randomtext;sp_internal_label;timestamp
+1623871499055;122.0;62.0;c;;1.623871499055E12
+1623871500059;123.0;46.0;a;;1.623871500059E12
+1623871501064;124.0;41.0;b;;1.623871501064E12
+1623871502070;125.0;41.0;b;;1.62387150207E12
+1623871503078;126.0;22.0;b;;1.623871503078E12
+1623871504082;127.0;56.0;a;;1.623871504082E12
+1623871505084;128.0;95.0;b;;1.623871505084E12
+1623871506086;129.0;77.0;a;;1.623871506086E12
+1623871507091;130.0;85.0;b;;1.623871507091E12
+1623871508093;131.0;22.0;a;;1.623871508093E12
diff --git a/ui/cypress/fixtures/pipelineElement/fieldRenamer1/input.csv 
b/ui/cypress/fixtures/pipelineElement/fieldRenamer1/input.csv
new file mode 100644
index 0000000..2420ef5
--- /dev/null
+++ b/ui/cypress/fixtures/pipelineElement/fieldRenamer1/input.csv
@@ -0,0 +1,11 @@
+timestamp;count;randomnumber;randomtext
+1623871499055;122.0;62.0;c
+1623871500059;123.0;46.0;a
+1623871501064;124.0;41.0;b
+1623871502070;125.0;41.0;b
+1623871503078;126.0;22.0;b
+1623871504082;127.0;56.0;a
+1623871505084;128.0;95.0;b
+1623871506086;129.0;77.0;a
+1623871507091;130.0;85.0;b
+1623871508093;131.0;22.0;a
diff --git a/ui/cypress/fixtures/pipelineElement/numericalFilter1/expected.csv 
b/ui/cypress/fixtures/pipelineElement/numericalFilter1/expected.csv
new file mode 100644
index 0000000..4695aee
--- /dev/null
+++ b/ui/cypress/fixtures/pipelineElement/numericalFilter1/expected.csv
@@ -0,0 +1,6 @@
+time;count;randomnumber;randomtext;sp_internal_label;timestamp
+1623871499055;122.0;62.0;c;;1.623871499055E12
+1623871504082;127.0;56.0;a;;1.623871504082E12
+1623871505084;128.0;95.0;b;;1.623871505084E12
+1623871506086;129.0;77.0;a;;1.623871506086E12
+1623871507091;130.0;85.0;b;;1.623871507091E12
diff --git a/ui/cypress/fixtures/pipelineElement/numericalFilter1/input.csv 
b/ui/cypress/fixtures/pipelineElement/numericalFilter1/input.csv
new file mode 100644
index 0000000..2420ef5
--- /dev/null
+++ b/ui/cypress/fixtures/pipelineElement/numericalFilter1/input.csv
@@ -0,0 +1,11 @@
+timestamp;count;randomnumber;randomtext
+1623871499055;122.0;62.0;c
+1623871500059;123.0;46.0;a
+1623871501064;124.0;41.0;b
+1623871502070;125.0;41.0;b
+1623871503078;126.0;22.0;b
+1623871504082;127.0;56.0;a
+1623871505084;128.0;95.0;b
+1623871506086;129.0;77.0;a
+1623871507091;130.0;85.0;b
+1623871508093;131.0;22.0;a
diff --git a/ui/cypress/integration/adapter/fileStream.ts 
b/ui/cypress/integration/adapter/fileStream.ts
new file mode 100644
index 0000000..fea214f
--- /dev/null
+++ b/ui/cypress/integration/adapter/fileStream.ts
@@ -0,0 +1,27 @@
+import {AdapterUtils} from '../../support/utils/AdapterUtils';
+import {FileManagementUtils} from '../../support/utils/FileManagementUtils';
+import {GenericAdapterBuilder} from 
'../../support/builder/GenericAdapterBuilder';
+
+describe('Test File Stream Adapter', function () {
+
+    it('Login', function () {
+        cy.login();
+    });
+
+    FileManagementUtils.addFile('fileTest/random.csv');
+
+    const adapterInput = GenericAdapterBuilder
+        .create('File_Stream')
+        .setName('File Stream Adapter Test')
+        .setTimestampProperty('timestamp')
+        .addProtocolInput('input', 'speed','1')
+        .addProtocolInput('checkbox', 'replaceTimestamp','check')
+        .setFormat('csv')
+        .addFormatInput('input', 'delimiter',';')
+        .addFormatInput('checkbox', 'header','check')
+        .build();
+
+    AdapterUtils.testGenericStreamAdapter(adapterInput);
+
+    FileManagementUtils.deleteFile();
+});
\ No newline at end of file
diff --git a/ui/cypress/integration/adapter/machineDataSimulator.ts 
b/ui/cypress/integration/adapter/machineDataSimulator.ts
new file mode 100644
index 0000000..b04a85d
--- /dev/null
+++ b/ui/cypress/integration/adapter/machineDataSimulator.ts
@@ -0,0 +1,14 @@
+import {AdapterUtils} from '../../support/utils/AdapterUtils';
+import {SpecificAdapterBuilder} from 
'../../support/builder/SpecificAdapterBuilder';
+
+describe('Test Random Data Simulator Stream Adapter', function () {
+
+    const adapterInput = SpecificAdapterBuilder
+        .create('Machine_Data_Simulator')
+        .setName('Machine Data Simulator Test')
+        .addInput('input', 'wait-time-ms','1000')
+        .build();
+
+    AdapterUtils.testSpecificStreamAdapter(adapterInput);
+
+});
\ No newline at end of file
diff --git a/ui/cypress/integration/adapter/randomDataSimulatorStream.spec.ts 
b/ui/cypress/integration/adapter/randomDataSimulatorStream.spec.ts
new file mode 100644
index 0000000..72505a2
--- /dev/null
+++ b/ui/cypress/integration/adapter/randomDataSimulatorStream.spec.ts
@@ -0,0 +1,15 @@
+import {UserInput} from '../../support/model/UserInput';
+import {AdapterUtils} from '../../support/utils/AdapterUtils';
+import {SpecificAdapterInput} from '../../support/model/SpecificAdapterInput';
+import {SpecificAdapterBuilder} from 
'../../support/builder/SpecificAdapterBuilder';
+
+describe('Test Random Data Simulator Stream Adapter', function () {
+
+    const adapterInput = SpecificAdapterBuilder
+        .create('Random_Data_Simulator_\\(Stream\\)')
+        .setName('Random Data Simulator Adapter Test')
+        .addInput('input', 'wait-time-ms','1000')
+        .build();
+
+    AdapterUtils.testSpecificStreamAdapter(adapterInput);
+});
diff --git a/ui/cypress/integration/dataexplorer/testDataExplorer.ts 
b/ui/cypress/integration/dataexplorer/testDataExplorer.ts
new file mode 100644
index 0000000..bccdc11
--- /dev/null
+++ b/ui/cypress/integration/dataexplorer/testDataExplorer.ts
@@ -0,0 +1,11 @@
+import {DataLakeUtils} from '../../support/utils/DataLakeUtils';
+
+describe('Test File Management', function () {
+    it('Login', function () {
+        cy.login();
+
+
+    });
+
+
+});
\ No newline at end of file
diff --git a/ui/cypress/integration/fileManagement/testFileManagement.ts 
b/ui/cypress/integration/fileManagement/testFileManagement.ts
new file mode 100644
index 0000000..9dbff1a
--- /dev/null
+++ b/ui/cypress/integration/fileManagement/testFileManagement.ts
@@ -0,0 +1,16 @@
+import {FileManagementUtils} from '../../support/utils/FileManagementUtils';
+
+describe('Test File Management', function () {
+    it('Login', function () {
+        cy.login();
+    });
+
+    FileManagementUtils.addFile('fileTest/test.csv')
+
+    FileManagementUtils.deleteFile()
+
+    it('Logout', function () {
+        cy.logout();
+    });
+
+});
\ No newline at end of file
diff --git a/ui/cypress/integration/pipeline/pipelineTest.ts 
b/ui/cypress/integration/pipeline/pipelineTest.ts
new file mode 100644
index 0000000..eb53c38
--- /dev/null
+++ b/ui/cypress/integration/pipeline/pipelineTest.ts
@@ -0,0 +1,32 @@
+import {UserInput} from '../../support/model/UserInput';
+import {AdapterUtils} from '../../support/utils/AdapterUtils';
+import {PipelineUtils} from '../../support/utils/PipelineUtils';
+import {PipelineElementBuilder} from 
'../../support/builder/PipelineElementBuilder';
+import {PipelineBuilder} from '../../support/builder/PipelineBuilder';
+
+describe('Test Random Data Simulator Stream Adapter', function () {
+
+    it('Login', function () {
+        cy.login();
+    });
+
+    const adapterName = 'simulator';
+    AdapterUtils.addMachineDataSimulator(adapterName);
+
+    const pipelineInput = PipelineBuilder.create('Pipeline Test')
+        .addSource(adapterName)
+        .addProcessingElement(
+            PipelineElementBuilder.create('field_renamer')
+                .addInput('drop-down', 'convert-property','timestamp')
+                .addInput('input', 'field-name','t')
+                .build())
+        .addSink(
+            PipelineElementBuilder.create('dashboard_sink')
+                .addInput('input', 'visualization-name','Demo')
+                .build())
+        .build();
+
+    PipelineUtils.testPipeline(pipelineInput);
+
+    AdapterUtils.deleteAdapter();
+});
\ No newline at end of file
diff --git a/ui/cypress/integration/pipelineElement/fieldRenamerTest1.ts 
b/ui/cypress/integration/pipelineElement/fieldRenamerTest1.ts
new file mode 100644
index 0000000..cf81d0d
--- /dev/null
+++ b/ui/cypress/integration/pipelineElement/fieldRenamerTest1.ts
@@ -0,0 +1,21 @@
+import {ProcessingElementTestUtils} from 
'../../support/utils/ProcessingElementTestUtils';
+import {PipelineElementBuilder} from 
'../../support/builder/PipelineElementBuilder';
+
+describe('Test Field Renamer 1', function () {
+
+    it('Login', function () {
+        cy.login();
+    });
+
+    // Config
+    const testName = 'fieldRenamer1'
+    const inputFile = 'pipelineElement/fieldRenamer1/input.csv';
+    const expectedResultFile = 'pipelineElement/fieldRenamer1/expected.csv';
+
+    const processor = PipelineElementBuilder.create('field_renamer')
+        .addInput('drop-down', 'convert-property','count')
+        .addInput('input', 'field-name','newname')
+        .build();
+
+    ProcessingElementTestUtils.testElement(testName, inputFile, 
expectedResultFile, processor);
+});
\ No newline at end of file
diff --git a/ui/cypress/integration/pipelineElement/numericalFilterTest1.ts 
b/ui/cypress/integration/pipelineElement/numericalFilterTest1.ts
new file mode 100644
index 0000000..e67d40f
--- /dev/null
+++ b/ui/cypress/integration/pipelineElement/numericalFilterTest1.ts
@@ -0,0 +1,24 @@
+import {UserInput} from '../../support/model/UserInput';
+import {PipelineElementInput} from '../../support/model/PipelineElementInput';
+import {ProcessingElementTestUtils} from 
'../../support/utils/ProcessingElementTestUtils';
+import {PipelineElementBuilder} from 
'../../support/builder/PipelineElementBuilder';
+
+describe('Test Numerical Filter 1', function () {
+
+    it('Login', function () {
+        cy.login();
+    });
+
+    // Config
+    const testName = 'numericalFilter1'
+    const inputFile = 'pipelineElement/numericalFilter1/input.csv';
+    const expectedResultFile = 'pipelineElement/numericalFilter1/expected.csv';
+
+    const processor = PipelineElementBuilder.create('numerical_filter')
+        .addInput('drop-down', 'number-mapping','randomnumber')
+        .addInput('radio', 'operation','\\>')
+        .addInput('input', 'value','50')
+        .build();
+
+    ProcessingElementTestUtils.testElement(testName, inputFile, 
expectedResultFile, processor);
+});
\ No newline at end of file
diff --git a/ui/cypress/plugins/index.ts b/ui/cypress/plugins/index.ts
new file mode 100644
index 0000000..6fbea7f
--- /dev/null
+++ b/ui/cypress/plugins/index.ts
@@ -0,0 +1,16 @@
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+module.exports = (on, config) => {
+    // `on` is used to hook into various events Cypress emits
+  // `config` is the resolved Cypress config
+};
+
diff --git a/ui/cypress/support/builder/GenericAdapterBuilder.ts 
b/ui/cypress/support/builder/GenericAdapterBuilder.ts
new file mode 100644
index 0000000..fbac9f8
--- /dev/null
+++ b/ui/cypress/support/builder/GenericAdapterBuilder.ts
@@ -0,0 +1,59 @@
+import {UserInput} from '../model/UserInput';
+import {SpecificAdapterInput} from '../model/SpecificAdapterInput';
+import {GenericAdapterInput} from '../model/GenericAdapterInput';
+
+export class GenericAdapterBuilder {
+    genericAdapterInput: GenericAdapterInput;
+
+    constructor(type: string) {
+        this.genericAdapterInput = new GenericAdapterInput();
+        this.genericAdapterInput.adapterType = type;
+        this.genericAdapterInput.protocolConfiguration = [];
+        this.genericAdapterInput.formatConfiguration = [];
+    }
+
+    public static create(name: string) {
+        return new GenericAdapterBuilder(name);
+    }
+
+    public setName(name: string) {
+        this.genericAdapterInput.adapterName = name;
+        return this;
+    }
+
+    public setTimestampProperty(timestsmpProperty: string) {
+        this.genericAdapterInput.timestampProperty = timestsmpProperty;
+        return this;
+    }
+
+    public addProtocolInput(type: string, selector: string, value: string) {
+        const  userInput = new UserInput();
+        userInput.type = type;
+        userInput.selector = selector;
+        userInput.value = value;
+
+        this.genericAdapterInput.protocolConfiguration.push(userInput);
+
+        return this;
+    }
+
+    public setFormat(format: string) {
+       this.genericAdapterInput.format = format;
+       return this;
+    }
+
+    public addFormatInput(type: string, selector: string, value: string) {
+        const  userInput = new UserInput();
+        userInput.type = type;
+        userInput.selector = selector;
+        userInput.value = value;
+
+        this.genericAdapterInput.formatConfiguration.push(userInput);
+
+        return this;
+    }
+
+    build() {
+        return this.genericAdapterInput;
+    }
+}
\ No newline at end of file
diff --git a/ui/cypress/support/builder/PipelineBuilder.ts 
b/ui/cypress/support/builder/PipelineBuilder.ts
new file mode 100644
index 0000000..9bba754
--- /dev/null
+++ b/ui/cypress/support/builder/PipelineBuilder.ts
@@ -0,0 +1,39 @@
+import {UserInput} from '../model/UserInput';
+import {PipelineInput} from '../model/PipelineInput';
+import {PipelineElementInput} from '../model/PipelineElementInput';
+
+export class PipelineBuilder {
+    pipeline: PipelineInput;
+
+    constructor(name: string) {
+        this.pipeline = new PipelineInput();
+        this.pipeline.pipelineName = name;
+    }
+
+    public static create(name: string) {
+        return new PipelineBuilder(name);
+    }
+
+    public addSource(source: string) {
+        this.pipeline.dataSource = source;
+
+        return this;
+    }
+
+    public addProcessingElement(processingElement: PipelineElementInput) {
+        this.pipeline.processingElement = processingElement;
+
+        return this;
+    }
+
+    public addSink(sink: PipelineElementInput) {
+        this.pipeline.dataSink = sink;
+
+        return this;
+    }
+
+    build() {
+        return this.pipeline;
+    }
+
+}
diff --git a/ui/cypress/support/builder/PipelineElementBuilder.ts 
b/ui/cypress/support/builder/PipelineElementBuilder.ts
new file mode 100644
index 0000000..805e2db
--- /dev/null
+++ b/ui/cypress/support/builder/PipelineElementBuilder.ts
@@ -0,0 +1,32 @@
+import {UserInput} from '../model/UserInput';
+import {PipelineElementInput} from '../model/PipelineElementInput';
+
+export class PipelineElementBuilder {
+
+    pipelineElementInput: PipelineElementInput;
+
+    constructor(name: string) {
+        this.pipelineElementInput = new PipelineElementInput();
+        this.pipelineElementInput.name = name;
+        this.pipelineElementInput.config = [];
+    }
+
+    public static create(name: string) {
+        return new PipelineElementBuilder(name);
+    }
+
+    public addInput(type: string, selector: string, value: string) {
+        const  userInput = new UserInput();
+        userInput.type = type;
+        userInput.selector = selector;
+        userInput.value = value;
+
+        this.pipelineElementInput.config.push(userInput);
+
+        return this;
+    }
+
+    build() {
+        return this.pipelineElementInput;
+    }
+}
diff --git a/ui/cypress/support/builder/SpecificAdapterBuilder.ts 
b/ui/cypress/support/builder/SpecificAdapterBuilder.ts
new file mode 100644
index 0000000..3ce498e
--- /dev/null
+++ b/ui/cypress/support/builder/SpecificAdapterBuilder.ts
@@ -0,0 +1,43 @@
+import {UserInput} from '../model/UserInput';
+import {SpecificAdapterInput} from '../model/SpecificAdapterInput';
+
+export class SpecificAdapterBuilder {
+
+
+    specificAdapterInput: SpecificAdapterInput;
+
+    constructor(type: string) {
+        this.specificAdapterInput = new SpecificAdapterInput();
+        this.specificAdapterInput.adapterType = type;
+        this.specificAdapterInput.adapterConfiguration = [];
+    }
+
+    public static create(name: string) {
+        return new SpecificAdapterBuilder(name);
+    }
+
+    public setName(name: string) {
+        this.specificAdapterInput.adapterName = name;
+        return this;
+    }
+
+    public setTimestampProperty(timestsmpProperty: string) {
+        this.specificAdapterInput.timestampProperty = timestsmpProperty;
+        return this;
+    }
+
+    public addInput(type: string, selector: string, value: string) {
+        const  userInput = new UserInput();
+        userInput.type = type;
+        userInput.selector = selector;
+        userInput.value = value;
+
+        this.specificAdapterInput.adapterConfiguration.push(userInput);
+
+        return this;
+    }
+
+    build() {
+        return this.specificAdapterInput;
+    }
+}
\ No newline at end of file
diff --git a/ui/cypress/support/general/dataCy.ts 
b/ui/cypress/support/general/dataCy.ts
new file mode 100644
index 0000000..4926360
--- /dev/null
+++ b/ui/cypress/support/general/dataCy.ts
@@ -0,0 +1,14 @@
+declare global {
+    namespace Cypress {
+        interface Chainable {
+            /**
+             * Select cypress id's ([data-cy=...])
+             */
+            dataCy(value: string): Chainable<Element>
+        }
+    }
+}
+
+export const dataCy = (value) => {
+    return cy.get(`[data-cy=${value}]`)
+};
diff --git a/ui/cypress/support/general/login.ts 
b/ui/cypress/support/general/login.ts
new file mode 100644
index 0000000..a3bcfb3
--- /dev/null
+++ b/ui/cypress/support/general/login.ts
@@ -0,0 +1,22 @@
+declare global {
+    namespace Cypress {
+        interface Chainable {
+            /**
+             * Login into streampipes with standard test user
+             * @example cy.login();
+             */
+            login: typeof login;
+        }
+    }
+}
+
+export const login = () => {
+    Cypress.Cookies.defaults({
+        preserve: "JSESSIONID"
+    });
+
+    cy.request('POST', '/streampipes-backend/api/v2/admin/login', {
+        username: '[email protected]',
+        password: 'test1234'
+    });
+};
diff --git a/ui/cypress/support/general/logout.ts 
b/ui/cypress/support/general/logout.ts
new file mode 100644
index 0000000..a195a5b
--- /dev/null
+++ b/ui/cypress/support/general/logout.ts
@@ -0,0 +1,20 @@
+declare global {
+    namespace Cypress {
+        interface Chainable {
+            /**
+             * Logout of StreamPipes
+             * @example cy.logout();
+             */
+            logout: typeof logout;
+        }
+    }
+}
+
+export const logout = () => {
+    Cypress.Cookies.defaults({
+        preserve: "JSESSIONID"
+    });
+
+    cy.dataCy('sp-user-preferences').click();
+    cy.dataCy('sp-logout').click();
+};
\ No newline at end of file
diff --git a/ui/cypress/support/index.ts b/ui/cypress/support/index.ts
new file mode 100644
index 0000000..1f4d353
--- /dev/null
+++ b/ui/cypress/support/index.ts
@@ -0,0 +1,11 @@
+import {login} from './general/login';
+import {logout} from './general/logout';
+import {dataCy} from './general/dataCy';
+import 'cypress-file-upload';
+
+// General commands
+Cypress.Commands.add('login', login);
+Cypress.Commands.add('logout', logout);
+Cypress.Commands.add('dataCy', dataCy);
+
+
diff --git a/ui/cypress/support/model/AdapterInput.ts 
b/ui/cypress/support/model/AdapterInput.ts
new file mode 100644
index 0000000..73d54ee
--- /dev/null
+++ b/ui/cypress/support/model/AdapterInput.ts
@@ -0,0 +1,5 @@
+export class AdapterInput {
+    adapterType: string;
+    adapterName: string;
+    timestampProperty: string;
+}
\ No newline at end of file
diff --git a/ui/cypress/support/model/GenericAdapterInput.ts 
b/ui/cypress/support/model/GenericAdapterInput.ts
new file mode 100644
index 0000000..793f26c
--- /dev/null
+++ b/ui/cypress/support/model/GenericAdapterInput.ts
@@ -0,0 +1,8 @@
+import {UserInput} from './UserInput';
+import {AdapterInput} from './AdapterInput';
+
+export class GenericAdapterInput extends AdapterInput{
+    protocolConfiguration: UserInput[];
+    format: string;
+    formatConfiguration: UserInput[];
+}
\ No newline at end of file
diff --git a/ui/cypress/support/model/PipelineElementInput.ts 
b/ui/cypress/support/model/PipelineElementInput.ts
new file mode 100644
index 0000000..4c7a09b
--- /dev/null
+++ b/ui/cypress/support/model/PipelineElementInput.ts
@@ -0,0 +1,6 @@
+import {UserInput} from './UserInput';
+
+export class PipelineElementInput {
+    name: string;
+    config: UserInput[];
+}
\ No newline at end of file
diff --git a/ui/cypress/support/model/PipelineInput.ts 
b/ui/cypress/support/model/PipelineInput.ts
new file mode 100644
index 0000000..f830fd1
--- /dev/null
+++ b/ui/cypress/support/model/PipelineInput.ts
@@ -0,0 +1,10 @@
+import {PipelineElementInput} from './PipelineElementInput';
+
+export class PipelineInput {
+    pipelineName: string;
+    dataSource: string;
+
+    processingElement: PipelineElementInput;
+
+    dataSink: PipelineElementInput;
+}
\ No newline at end of file
diff --git a/ui/cypress/support/model/SpecificAdapterInput.ts 
b/ui/cypress/support/model/SpecificAdapterInput.ts
new file mode 100644
index 0000000..ffd3ab8
--- /dev/null
+++ b/ui/cypress/support/model/SpecificAdapterInput.ts
@@ -0,0 +1,6 @@
+import {UserInput} from './UserInput';
+import {AdapterInput} from './AdapterInput';
+
+export class SpecificAdapterInput extends AdapterInput{
+    adapterConfiguration: UserInput[];
+}
\ No newline at end of file
diff --git a/ui/cypress/support/model/UserInput.ts 
b/ui/cypress/support/model/UserInput.ts
new file mode 100644
index 0000000..fbf1451
--- /dev/null
+++ b/ui/cypress/support/model/UserInput.ts
@@ -0,0 +1,5 @@
+export class UserInput {
+    type: string | 'checkbox' | 'input' | 'file' | 'drop-down' | 'radio';
+    selector: string;
+    value: string;
+}
diff --git a/ui/cypress/support/utils/AdapterUtils.ts 
b/ui/cypress/support/utils/AdapterUtils.ts
new file mode 100644
index 0000000..1a2152a
--- /dev/null
+++ b/ui/cypress/support/utils/AdapterUtils.ts
@@ -0,0 +1,179 @@
+import {UserInput} from '../model/UserInput';
+import {StaticPropertyUtils} from './StaticPropertyUtils';
+import {SpecificAdapterInput} from '../model/SpecificAdapterInput';
+import {GenericAdapterInput} from '../model/GenericAdapterInput';
+import {SpecificAdapterBuilder} from '../builder/SpecificAdapterBuilder';
+
+export class AdapterUtils {
+
+    public static testSpecificStreamAdapter(adapterConfiguration: 
SpecificAdapterInput) {
+
+        AdapterUtils.goToConnect();
+
+        AdapterUtils.selectAdapter(adapterConfiguration.adapterType);
+
+        
AdapterUtils.configureAdapter(adapterConfiguration.adapterConfiguration);
+
+        AdapterUtils.finishEventSchemaConfiguration();
+
+        AdapterUtils.startStreamAdapter(adapterConfiguration.adapterName);
+
+        AdapterUtils.deleteAdapter();
+
+    }
+
+    public static testGenericStreamAdapter(adapterConfiguration: 
GenericAdapterInput) {
+
+        AdapterUtils.addGenericStreamAdapter(adapterConfiguration);
+
+        AdapterUtils.deleteAdapter();
+
+    }
+
+
+    public static addGenericStreamAdapter(adapterConfiguration: 
GenericAdapterInput) {
+        AdapterUtils.addGenericAdapter(adapterConfiguration);
+
+        AdapterUtils.startStreamAdapter(adapterConfiguration.adapterName);
+    }
+
+    public static addGenericSetAdapter(adapterConfiguration: 
GenericAdapterInput) {
+        AdapterUtils.addGenericAdapter(adapterConfiguration);
+
+        AdapterUtils.startSetAdapter(adapterConfiguration.adapterName);
+    }
+
+    private static addGenericAdapter(adapterConfiguration: 
GenericAdapterInput) {
+        AdapterUtils.goToConnect();
+
+        AdapterUtils.selectAdapter(adapterConfiguration.adapterType);
+
+        
AdapterUtils.configureAdapter(adapterConfiguration.protocolConfiguration);
+
+        AdapterUtils.configureFormat(adapterConfiguration);
+
+        
AdapterUtils.markPropertyAsTimestamp(adapterConfiguration.timestampProperty);
+
+        AdapterUtils.finishEventSchemaConfiguration();
+    }
+
+    public static addMachineDataSimulator(name: string) {
+
+        const configuration = SpecificAdapterBuilder
+            .create('Machine_Data_Simulator')
+            .setName(name)
+            .addInput('input', 'wait-time-ms','1000')
+            .build();
+
+        AdapterUtils.goToConnect();
+
+        AdapterUtils.selectAdapter(configuration.adapterType);
+
+        AdapterUtils.configureAdapter(configuration.adapterConfiguration);
+
+        AdapterUtils.finishEventSchemaConfiguration();
+
+        AdapterUtils.startStreamAdapter(configuration.adapterName);
+
+    }
+
+    private static goToConnect() {
+        it('Login', function () {
+            cy.login();
+        });
+
+        it('Go to StreamPipes connect', function () {
+            cy.visit('#/connect');
+        });
+    }
+
+    private static selectAdapter(name) {
+        it('Select adapter', function () {
+            cy.get('#' + name).click();
+        });
+    }
+
+    private static configureAdapter(configs: UserInput[]) {
+        it('Next Button should be disabled', function () {
+            cy.get('button').contains('Next').parent().should('be.disabled');
+        });
+
+        StaticPropertyUtils.input(configs);
+
+        it('Next Button should not be disabled', function () {
+            
cy.get('button').contains('Next').parent().should('not.be.disabled');
+        });
+
+        it('Click next', function () {
+            cy.get('button').contains('Next').parent().click();
+        });
+    }
+
+    private static configureFormat(adapterConfiguration: GenericAdapterInput) {
+        it('Select format', function () {
+            cy.dataCy(adapterConfiguration.format).click();
+        });
+
+        StaticPropertyUtils.input(adapterConfiguration.formatConfiguration);
+
+        it('Click next', function () {
+            // 
cy.dataCy('sp-format-selection-next-button').parent().should('not.be.disabled');
+            
cy.dataCy('sp-format-selection-next-button').contains('Next').parent().click();
+        });
+    }
+
+    private static markPropertyAsTimestamp(propertyName: string) {
+        it('Mark property as timestamp', function () {
+
+            // Edit timestamp
+            cy.dataCy('edit-' + propertyName).click();
+
+            // Mark as timestamp
+            cy.dataCy('sp-mark-as-timestamp').children().click();
+
+            // Close
+            cy.dataCy('sp-save-edit-property').click();
+        });
+    }
+
+    private static finishEventSchemaConfiguration() {
+        it('Click next', function () {
+            cy.get('[data-cy=sp-connect-schema-editor]', { timeout: 10000 
}).should('be.visible');
+            cy.get('#event-schema-next-button').click();
+        });
+    }
+
+    private static startStreamAdapter(name) {
+        AdapterUtils.startAdapter(name, 'sp-connect-adapter-live-preview');
+    }
+
+    private static startSetAdapter(name) {
+        AdapterUtils.startAdapter(name, 'sp-connect-adapter-set-success');
+    }
+
+    private static startAdapter(name ,successElement) {
+        it('Set adapter name', function () {
+            cy.get('[data-cy=sp-adapter-name]').type(name);
+        });
+
+        it('Start adapter', function () {
+            cy.get('#button-startAdapter').click();
+            cy.get('[data-cy=' + successElement + ']', { timeout: 10000 
}).should('be.visible');
+        });
+
+        it('Close adapter preview', function () {
+            cy.get('button').contains('Close').parent().click();
+        });
+    }
+
+    public static deleteAdapter() {
+        it('Delete adapter', function () {
+            cy.visit('#/connect');
+
+            cy.get('div').contains('My Adapters').parent().click();
+            cy.dataCy('delete').should('have.length', 1)
+            cy.dataCy('delete').click()
+            cy.get('[data-cy=delete]', { timeout: 10000 
}).should('have.length', 0);
+        });
+    }
+}
diff --git a/ui/cypress/support/utils/DataLakeUtils.ts 
b/ui/cypress/support/utils/DataLakeUtils.ts
new file mode 100644
index 0000000..6d85802
--- /dev/null
+++ b/ui/cypress/support/utils/DataLakeUtils.ts
@@ -0,0 +1,34 @@
+import * as CSV from 'csv-string';
+
+export class DataLakeUtils {
+
+    public static checkResults(dataLakeIndex: string, fileRoute: string) {
+
+        it('Validate result in datalake', function () {
+            const streamPipesUser = '[email protected]';
+            cy.request('GET', '/streampipes-backend/api/v3/users/' + 
streamPipesUser + '/datalake/data/' + dataLakeIndex + '/download?format=csv',
+                {'content-type': 
'application/octet-stream'}).should((response) => {
+                const expectedResultString = response.body;
+                cy.readFile(fileRoute).then((actualResultString) => {
+                    DataLakeUtils.resultEqual(actualResultString, 
expectedResultString);
+                });
+            });
+        });
+    }
+
+    private static resultEqual(actual: string, expected: string) {
+        const expectedResult = DataLakeUtils.parseCsv(expected);
+        const actualResult = DataLakeUtils.parseCsv(actual);
+        expect(expectedResult).to.deep.equal(actualResult);
+    }
+
+    private static  parseCsv(csv: string) {
+        const result = []
+        const index = CSV.readAll(csv, row => {
+            result.push(row);
+        });
+
+        return result;
+    }
+
+}
\ No newline at end of file
diff --git a/ui/cypress/support/utils/FileManagementUtils.ts 
b/ui/cypress/support/utils/FileManagementUtils.ts
new file mode 100644
index 0000000..c9e5606
--- /dev/null
+++ b/ui/cypress/support/utils/FileManagementUtils.ts
@@ -0,0 +1,33 @@
+
+export class FileManagementUtils {
+
+    public static addFile(filePath: string) {
+        it('Go to StreamPipes file management', function () {
+            cy.visit('#/files');
+        });
+
+        it('Open file upload dialog', function () {
+            cy.dataCy('sp-open-file-upload-dialog').click();
+        });
+
+        it('Upload file', function () {
+            // const filepath = 'fileTest/test.csv'
+            cy.dataCy('sp-file-management-file-input').attachFile(filePath);
+            cy.dataCy('sp-file-management-store-file').click()
+        });
+    }
+
+    public static deleteFile() {
+        it('Go to StreamPipes file management', function () {
+            cy.visit('#/files');
+        });
+        it('Check if file was uploaded and delete it', function () {
+            cy.dataCy('delete').should('have.length', 1)
+            cy.dataCy('delete').click()
+            cy.dataCy('confirm-delete').click()
+            cy.dataCy('delete').should('have.length', 0)
+        });
+    }
+
+
+}
\ No newline at end of file
diff --git a/ui/cypress/support/utils/PipelineUtils.ts 
b/ui/cypress/support/utils/PipelineUtils.ts
new file mode 100644
index 0000000..c22b304
--- /dev/null
+++ b/ui/cypress/support/utils/PipelineUtils.ts
@@ -0,0 +1,88 @@
+import {PipelineInput} from '../model/PipelineInput';
+import {UserInput} from '../model/UserInput';
+import {StaticPropertyUtils} from './StaticPropertyUtils';
+
+export class PipelineUtils {
+
+    public static testPipeline(pipelineInput: PipelineInput) {
+
+        PipelineUtils.addPipeline(pipelineInput);
+
+        PipelineUtils.deletePipeline();
+
+    }
+
+    public static addPipeline(pipelineInput: PipelineInput) {
+
+        PipelineUtils.goToPipelineEditor();
+
+        PipelineUtils.selectDataStream(pipelineInput);
+
+        PipelineUtils.configurePipeline(pipelineInput);
+
+        PipelineUtils.startPipeline(pipelineInput);
+
+    }
+
+
+    private static goToPipelineEditor() {
+        it('Go to StreamPipes connect', function () {
+            cy.visit('#/editor');
+        });
+    }
+
+    private static selectDataStream(pipelineInput: PipelineInput) {
+        it('Select a stream', function () {
+            cy.dataCy('sp-editor-add-pipeline-element').click();
+            cy.dataCy(pipelineInput.dataSource).click();
+        });
+    }
+
+
+    private static configurePipeline(pipelineInput: PipelineInput) {
+
+        it('Select processor', function () {
+            cy.get('[data-cy=sp-possible-elements-' + pipelineInput.dataSource 
+']', { timeout: 10000 }).click();
+            cy.dataCy('sp-compatible-elements-' + 
pipelineInput.processingElement.name).click();
+        });
+
+        StaticPropertyUtils.input(pipelineInput.processingElement.config);
+
+        it('Save configuration', function () {
+            cy.dataCy('sp-element-configuration-save').click();
+        });
+
+        it('Select sink', function () {
+            cy.dataCy('sp-possible-elements-' + 
pipelineInput.processingElement.name).click();
+            cy.dataCy('sp-compatible-elements-'+ 
pipelineInput.dataSink.name).click();
+        });
+
+        StaticPropertyUtils.input(pipelineInput.dataSink.config);
+
+        it('Save sink configuration', function () {
+            cy.dataCy('sp-element-configuration-save').click();
+        });
+
+    }
+
+    private static startPipeline(pipelineInput: PipelineInput) {
+        it('Save and start pipeline', function () {
+            cy.dataCy('sp-editor-save-pipeline').click();
+            
cy.dataCy('sp-editor-pipeline-name').type(pipelineInput.pipelineName);
+            
cy.dataCy('sp-editor-checkbox-start-immediately').children().click();
+            cy.dataCy('sp-editor-save').click();
+            cy.get('[data-cy=sp-pipeline-started-dialog]', { timeout: 10000 
}).should('be.visible');
+            cy.get('[data-cy=sp-pipeline-dialog-close]', { timeout: 10000 
}).click();
+        });
+    }
+
+    public static deletePipeline() {
+        it('Delete pipeline', function () {
+            cy.visit('#/pipelines');
+            cy.dataCy('delete').should('have.length', 1)
+            cy.dataCy('delete').click()
+            cy.dataCy('sp-pipeline-stop-and-delete').click()
+            cy.get('[data-cy=delete]', { timeout: 10000 
}).should('have.length', 0);
+        });
+    }
+}
diff --git a/ui/cypress/support/utils/ProcessingElementTestUtils.ts 
b/ui/cypress/support/utils/ProcessingElementTestUtils.ts
new file mode 100644
index 0000000..967b3ab
--- /dev/null
+++ b/ui/cypress/support/utils/ProcessingElementTestUtils.ts
@@ -0,0 +1,58 @@
+import {FileManagementUtils} from './FileManagementUtils';
+import {AdapterUtils} from './AdapterUtils';
+import {PipelineElementInput} from '../model/PipelineElementInput';
+import {PipelineUtils} from './PipelineUtils';
+import {DataLakeUtils} from './DataLakeUtils';
+import {GenericAdapterBuilder} from '../builder/GenericAdapterBuilder';
+import {PipelineBuilder} from '../builder/PipelineBuilder';
+import {PipelineElementBuilder} from '../builder/PipelineElementBuilder';
+
+export class ProcessingElementTestUtils {
+
+    public static testElement(testName: string, inputFile: string, 
expectedResultFile: string, processor: PipelineElementInput) {
+        // Test
+        FileManagementUtils.addFile(inputFile);
+
+        const dataLakeIndex = testName.toLowerCase();
+
+        const adapterName = testName.toLowerCase();
+
+        // Build adapter
+        const adapterInput = GenericAdapterBuilder
+            .create('File_Set')
+            .setName(adapterName)
+            .setTimestampProperty('timestamp')
+            .addProtocolInput('input', 'interval-key','0')
+            .setFormat('csv')
+            .addFormatInput('input', 'delimiter',';')
+            .addFormatInput('checkbox', 'header','check')
+            .build();
+
+        AdapterUtils.addGenericSetAdapter(adapterInput);
+
+        // Build Pipeline
+        const pipelineInput = PipelineBuilder.create(testName)
+            .addSource(adapterName)
+            .addProcessingElement(processor)
+            .addSink(
+                PipelineElementBuilder.create('data_lake')
+                    .addInput('input', 'db_measurement',dataLakeIndex)
+                    .build())
+            .build();
+
+        PipelineUtils.addPipeline(pipelineInput);
+
+        // // Wait
+        it('Wait till data is stored', function () {
+            cy.wait(10000)
+        });
+
+        DataLakeUtils.checkResults(dataLakeIndex, 'cypress/fixtures/' + 
expectedResultFile);
+
+        PipelineUtils.deletePipeline();
+
+        AdapterUtils.deleteAdapter();
+
+        FileManagementUtils.deleteFile();
+    }
+}
\ No newline at end of file
diff --git a/ui/cypress/support/utils/StaticPropertyUtils.ts 
b/ui/cypress/support/utils/StaticPropertyUtils.ts
new file mode 100644
index 0000000..7fc414f
--- /dev/null
+++ b/ui/cypress/support/utils/StaticPropertyUtils.ts
@@ -0,0 +1,21 @@
+import {UserInput} from '../model/UserInput';
+
+export class StaticPropertyUtils {
+
+    public static input(configs: UserInput[]) {
+
+        it('Configure Properties', function () {
+            configs.forEach(config => {
+                if (config.type == 'checkbox') {
+                    cy.dataCy(config.selector).children().click();
+                } else if (config.type == 'drop-down') {
+                    
cy.dataCy(config.selector).click().get('mat-option').contains(config.value).click();
+                }  else if (config.type == 'radio') {
+                    cy.dataCy(config.selector + config.value ).click();
+                } else {
+                    cy.dataCy(config.selector).type(config.value);
+                }
+            });
+        });
+    }
+}
diff --git a/ui/cypress/tsconfig.json b/ui/cypress/tsconfig.json
new file mode 100644
index 0000000..abba62d
--- /dev/null
+++ b/ui/cypress/tsconfig.json
@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "target": "es5",
+    "lib": ["es5", "dom"],
+    "types": ["cypress"]
+  },
+  "include": [
+    "**/*.ts",
+    "node_modules/@types/node/globals.d.ts"
+  ]
+}
\ No newline at end of file
diff --git a/ui/package.json b/ui/package.json
index efc485d..a0f6651 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -14,7 +14,8 @@
     "build": "node ./deployment/prebuild.js && set 
NODE_OPTIONS=--max-old-space-size=10192 && ng build --prod",
     "build-rc": "node ./deployment/prebuild.js rel && set 
NODE_OPTIONS=--max-old-space-size=10192 && ng build --prod",
     "lint": "tslint -c tslint.json 'src/**/*.ts*",
-    "analyze": "webpack-bundle-analyzer dist/stats.json"
+    "analyze": "webpack-bundle-analyzer dist/stats.json",
+    "test-e2e": "cypress open"
   },
   "dependencies": {
     "@angular/animations": "11.0.5",
@@ -106,6 +107,9 @@
     "compression-webpack-plugin": "^3.0.0",
     "copy-webpack-plugin": "^5.1.2",
     "css-loader": "^0.28.11",
+    "csv-string": "4.0.1",
+    "cypress": "^7.5.0",
+    "cypress-file-upload": "5.0.7",
     "extract-text-webpack-plugin": "^3.0.2",
     "file-loader": "^1.1.6",
     "html-webpack-plugin": "^3.2.0",

Reply via email to