diff --git a/tests/karate/docs/README.md b/tests/karate/docs/README.md index 3eae6f0f91..2d0b417ce0 100644 --- a/tests/karate/docs/README.md +++ b/tests/karate/docs/README.md @@ -11,8 +11,7 @@ To create test cases, we handcraft messages with either valid or invalid message The mock components then send these messages to the component being tested. We then check that the responses the mock components receive are as expected. -## Architecture -### Features and Scenarios +## Features and Scenarios Karate test cases are called scenarios and they are grouped within different feature files. Each feature file tests a different message type (i.e. electionOpen, createRollCall etc.). @@ -24,7 +23,7 @@ For instance, `publish` creates a message of type publish that contains some hig - **Then**: Asserts that the action taken in the 'When' step has the expected outcome. - **And**: Connector that can be used after any of the other keywords. -### Background section +## Background section Code defined in the background section of a feature file runs before each scenario. This is especially useful for: - **Sharing scopes with other features**: The call to `read(classpath: "path/to/feature")` is used to make the current feature share the same scope as the feature that is called. @@ -35,6 +34,7 @@ For instance, reading `mockClient.feature` exposes functions like `createMockFro - **Setting up previous steps necessary for a test**: For instance, before roll call messages can be tested, a LAO needs to be created first. `simpleScenarios.feature` contains many such useful setup steps. +## Backend tests architecture ### Data model To generate valid message data for JSON payloads dynamically, a simplified version of the model is implemented in Java code. Mock components can create valid objects (for instance LAO, RollCall, Elections etc.), that can be used to handcraft messages. @@ -59,7 +59,7 @@ This class provides the functions to create model data for LAOs, roll calls, ele For instance, here the created organizer and LAO are passed to the `createLaoScenario`. - The name tag `@createRollCall1` is used to call individual scenarios on the command line, see [Running the Tests](#running-the-tests) -``` +```gherkin Feature: Create a Roll Call Background: @@ -68,8 +68,8 @@ Feature: Create a Roll Call * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() - * def validRollCall = organizer.createValidRollCall(lao) + * def lao = organizer.generateValidLao() + * def validRollCall = organizer.generateValidRollCall(lao) * call read(createLaoScenario) { organizer: '#(organizer)', lao: '#(lao)' } @createRollCall1 @@ -94,6 +94,37 @@ Feature: Create a Roll Call And match organizer.receiveNoMoreResponses() == true ``` +## Frontend tests architecture +### Files +* `utils` + * `constants.feature`: Contains all the necessary constants. Usually called at the beginning of any feature. + * `android.feature` and `web.feature`: Contain platform specific scenarios that will be used by the actual tests. Both files should implement the same scenarios. + * `platform.feature`: A simple wrapper around `android.feature` and `web.feature` that allows you to call the right scenario depending on the current env you are testing for. (i.e. if you set `karate.env=web`, it will call scenarios from `web.feature`) + * `mock_client.feature`: Allows you to create a mock client via `createMockClient`. Automatically stops all clients after each scenario. +* `features`: The actual tests + +### Example: Lao join +```gherkin +Feature: LAO + Background: + # Get the needed utils + * call read('classpath:fe/utils/constants.feature') + * call read(MOCK_CLIENT_FEATURE) + + @name=lao_join + Scenario: Manually connect to an existing LAO + # Use a mock client to create a random lao + Given def organizer = createMockClient() + And def lao = organizer.createLao() + # Call platform specific code with some parameters + # i.e. if karate.env=web, equals to call('classpath:fe/utils/web.feature@name=lao_join') { params: { lao: "#(lao)" } } + When call read(PLATFORM_FEATURE) { name: "#(JOIN_LAO)", params: { lao: "#(lao)" } } + # Actual test: The user should not have access to the button + Then assert !exists(event_create_button) + And screenshot() +``` + + ## First Setup ### All @@ -173,16 +204,34 @@ mvn test -Dkarate.options="--tags @scenarioTagName" With the Karate plugin for IntelliJ, the full tests can also be run directly from inside IDE in the `BackEndTest` class. +### Web Front-end +Build the app with `npm run build-web` in the corresponding directory. + +Launch the Appium server (with `appium`). + +Finally run the tests: +* All tests: `mvn test -Dkarate.env=web -Dtest=FrontEndTest#fullTest` +* One feature: `mvn test -Dkarate.env=web -Dtest=FrontEndTest#fullTest -Dkarate.options="classpath:fe/features/.feature"` +* One scenario: `mvn test -Dkarate.env=web -Dtest=FrontEndTest#fullTest -Dkarate.options="classpath:fe/features/.feature --tags=@name="` + +The following options are available (option names must be prefixed by `-D`). +| Name | Description | Default | +|--------------|------------------------------------------------|-------------------------------------------| +| browser | One of 'chrome', 'safari', 'edge' or 'firefox' | 'chrome' | +| url | URL of the web app | 'file:../../fe1-web/web-build/index.html' | +| screenWidth | Width of the browser | 1920 | +| screenHeight | Height of the browser | 1080 | +| serverURL | Client URL of the backend server | 'ws://localhost:9000/client' for the web and 'ws://10.0.2.2:9000/client' for android | ### Android Front-end Build the application by running `./gradlew assembleDebug` in the corresponding directory. -Then, start an emulator from Android Studio and launch the Appium server (using the command `appium`). +Launch the Appium server (with `appium`). -Finally run the tests. -``` -mvn test -Dkarate.env=android -Dtest=FrontEndTest#fullTest -``` +Finally run the tests: +* All tests: `mvn test -Dkarate.env=android -Dtest=FrontEndTest#fullTest` +* One feature: `mvn test -Dkarate.env=android -Dtest=FrontEndTest#fullTest -Dkarate.options="classpath:fe/features/.feature"` +* One scenario: `mvn test -Dkarate.env=android -Dtest=FrontEndTest#fullTest -Dkarate.options="classpath:fe/features/.feature --tags=@name="` In case you have multiple emulators running, you may specify one by avd id. To find the avd id of some emulator, go to the Device Manager (`Tools -> Device Manager`) and follow the steps in the image below. @@ -194,20 +243,8 @@ mvn test -Dkarate.env=android -Davd= -Dtest=FrontEndTest#fullTest #e.g. mvn test -Dkarate.env=android -Davd=Galaxy_Note_9_API_29 -Dtest=FrontEndTest#fullTest ``` -### Web Front-end -Build the app with `npm run build-web` in the corresponding directory. - -Launch the Appium server (with `appium`). - -Run the tests. -``` -mvn test -Dkarate.env=web -Dtest=FrontEndTest#fullTest -``` - The following options are available (option names must be prefixed by `-D`). | Name | Description | Default | |--------------|------------------------------------------------|-------------------------------------------| -| browser | One of 'chrome', 'safari', 'edge' or 'firefox' | 'chrome' | -| url | URL of the web app | 'file:../../fe1-web/web-build/index.html' | -| screenWidth | Width of the browser | 1920 | -| screenHeight | Height of the browser | 1080 | +| avd | Name of the android emulator | Choosen automatically by appium | +| serverURL | Client URL of the backend server | 'ws://localhost:9000/client' for the web and 'ws://10.0.2.2:9000/client' for android | diff --git a/tests/karate/src/test/java/be/features/LAO/create.feature b/tests/karate/src/test/java/be/features/LAO/create.feature index 5d91b5e892..57b5b46fe9 100644 --- a/tests/karate/src/test/java/be/features/LAO/create.feature +++ b/tests/karate/src/test/java/be/features/LAO/create.feature @@ -8,7 +8,7 @@ Feature: Create a pop LAO * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def validLao = organizer.createValidLao() + * def validLao = organizer.generateValidLao() @create1 Scenario: Create Lao request with empty lao name should fail with an error response diff --git a/tests/karate/src/test/java/be/features/LAO/update.feature b/tests/karate/src/test/java/be/features/LAO/update.feature index fd093798bf..49e62143ea 100644 --- a/tests/karate/src/test/java/be/features/LAO/update.feature +++ b/tests/karate/src/test/java/be/features/LAO/update.feature @@ -7,7 +7,7 @@ Feature: Update a LAO * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() + * def lao = organizer.generateValidLao() # This call executes all the steps to create a valid lao on the server before every scenario # (lao creation, subscribe, catchup) diff --git a/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature b/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature index 8cd2db3f5f..04e8fd8d2a 100644 --- a/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature +++ b/tests/karate/src/test/java/be/features/decentralizedCom/getMessagesById.feature @@ -8,7 +8,7 @@ Feature: Request messages by id from other servers * call read(mockClientFeature) * def mockBackend = call createMockBackend * def mockFrontend = call createMockFrontend - * def lao = mockFrontend.createValidLao() + * def lao = mockFrontend.generateValidLao() # Create the template for heartbeat message # This is used in combination with 'eval' to dynamically resolve the channel keys in the heartbeat JSON @@ -63,7 +63,7 @@ Feature: Request messages by id from other servers # trigger a getMessagesById anymore @getMessagesById4 Scenario: Server should not request messages that it already has - Given def validRollCall = mockFrontend.createValidRollCall(lao) + Given def validRollCall = mockFrontend.generateValidRollCall(lao) And def validCreateRollCall = """ { diff --git a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature index 8345301539..3a9b881125 100644 --- a/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature +++ b/tests/karate/src/test/java/be/features/decentralizedCom/heartbeat.feature @@ -8,8 +8,8 @@ Feature: Send heartbeats to other servers * call read(mockClientFeature) * def mockBackend = call createMockBackend * def mockFrontend = call createMockFrontend - * def lao = mockFrontend.createValidLao() - * def validRollCall = mockFrontend.createValidRollCall(lao) + * def lao = mockFrontend.generateValidLao() + * def validRollCall = mockFrontend.generateValidRollCall(lao) # This call executes all the steps to create a valid lao on the server before every scenario # (lao creation, subscribe, catchup) diff --git a/tests/karate/src/test/java/be/features/digitalCash/transaction.feature b/tests/karate/src/test/java/be/features/digitalCash/transaction.feature index c23a2b17c3..c5fce21b8e 100644 --- a/tests/karate/src/test/java/be/features/digitalCash/transaction.feature +++ b/tests/karate/src/test/java/be/features/digitalCash/transaction.feature @@ -7,8 +7,8 @@ Feature: Simple Transactions for digital cash * call read(mockClientFeature) * def organizer = call createMockFrontend * def recipient = call createMockFrontend - * def lao = organizer.createValidLao() - * def rollCall = organizer.createValidRollCall(lao) + * def lao = organizer.generateValidLao() + * def rollCall = organizer.generateValidRollCall(lao) # This call executes all the steps to set up a lao, complete a roll call and subscribe to the coin channel * call read(setupCoinChannelScenario) { organizer: '#(organizer)', lao: '#(lao)', rollCall: '#(rollCall)' } diff --git a/tests/karate/src/test/java/be/features/election/castVote.feature b/tests/karate/src/test/java/be/features/election/castVote.feature index e924b04db5..83f0c46bb5 100644 --- a/tests/karate/src/test/java/be/features/election/castVote.feature +++ b/tests/karate/src/test/java/be/features/election/castVote.feature @@ -6,9 +6,9 @@ Feature: Cast a vote * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() - * def rollCall = organizer.createValidRollCall(lao) - * def election = organizer.createValidElection(lao) + * def lao = organizer.generateValidLao() + * def rollCall = organizer.generateValidRollCall(lao) + * def election = organizer.generateValidElection(lao) * def question = election.createQuestion() # This call executes all the steps to set up a lao, complete a roll call and open an election with one question @@ -71,7 +71,7 @@ Feature: Cast a vote # upon casting a vote @castVote3 Scenario: Casting a valid vote on non existent election should return an error - Given def newElection = organizer.createValidElection(lao) + Given def newElection = organizer.generateValidElection(lao) And def newQuestion = newElection.createQuestion() And def newVote = newQuestion.createVote(0) And def newCastVote = newElection.castVote(newVote) diff --git a/tests/karate/src/test/java/be/features/election/electionEnd.feature b/tests/karate/src/test/java/be/features/election/electionEnd.feature index 5918c09967..ffe927b29b 100644 --- a/tests/karate/src/test/java/be/features/election/electionEnd.feature +++ b/tests/karate/src/test/java/be/features/election/electionEnd.feature @@ -6,9 +6,9 @@ Feature: Terminate an election * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() - * def rollCall = organizer.createValidRollCall(lao) - * def election = organizer.createValidElection(lao) + * def lao = organizer.generateValidLao() + * def rollCall = organizer.generateValidRollCall(lao) + * def election = organizer.generateValidElection(lao) * def question = election.createQuestion() # This call executes all the steps to set up a lao, complete a roll call, open an election and cast a vote diff --git a/tests/karate/src/test/java/be/features/election/electionOpen.feature b/tests/karate/src/test/java/be/features/election/electionOpen.feature index b51b7ebedf..bb5e70695e 100644 --- a/tests/karate/src/test/java/be/features/election/electionOpen.feature +++ b/tests/karate/src/test/java/be/features/election/electionOpen.feature @@ -6,9 +6,9 @@ Feature: Open an Election * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() - * def rollCall = organizer.createValidRollCall(lao) - * def election = organizer.createValidElection(lao) + * def lao = organizer.generateValidLao() + * def rollCall = organizer.generateValidRollCall(lao) + * def election = organizer.generateValidElection(lao) * def question = election.createQuestion() # This call executes all the steps to set up a lao, complete a roll call and create an election with one question @@ -38,7 +38,7 @@ Feature: Open an Election # upon an open election message @electionOpen2 Scenario: Opening the election without a setup should result in an error - Given def newElection = organizer.createValidElection(lao) + Given def newElection = organizer.generateValidElection(lao) And def newElectionOpen = newElection.open() And def validElectionOpen = """ diff --git a/tests/karate/src/test/java/be/features/election/electionSetup.feature b/tests/karate/src/test/java/be/features/election/electionSetup.feature index 467d2e96bb..c6850b86da 100644 --- a/tests/karate/src/test/java/be/features/election/electionSetup.feature +++ b/tests/karate/src/test/java/be/features/election/electionSetup.feature @@ -6,9 +6,9 @@ Feature: Setup an Election * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() - * def rollCall = organizer.createValidRollCall(lao) - * def election = organizer.createValidElection(lao) + * def lao = organizer.generateValidLao() + * def rollCall = organizer.generateValidRollCall(lao) + * def election = organizer.generateValidElection(lao) * def question = election.createQuestion() # This call executes all the steps to set up a lao and complete a roll call, to get a valid pop token diff --git a/tests/karate/src/test/java/be/features/rollCall/closeRollCall.feature b/tests/karate/src/test/java/be/features/rollCall/closeRollCall.feature index f4909f9fd2..c5828e0cad 100644 --- a/tests/karate/src/test/java/be/features/rollCall/closeRollCall.feature +++ b/tests/karate/src/test/java/be/features/rollCall/closeRollCall.feature @@ -8,8 +8,8 @@ Feature: Close a Roll Call * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() - * def rollCall = organizer.createValidRollCall(lao) + * def lao = organizer.generateValidLao() + * def rollCall = organizer.generateValidRollCall(lao) # This call executes all the steps to open a valid roll call on the server before every scenario # (lao creation, subscribe, catchup, roll call creation, roll call open) @@ -78,7 +78,7 @@ Feature: Close a Roll Call @closeRollCall4 Scenario: Closing a Roll Call that was not opened on the server returns an error - Given def newRollCall = organizer.createValidRollCall(lao) + Given def newRollCall = organizer.generateValidRollCall(lao) # This call creates the new roll call on the server without opening it And call read(createRollCallScenario) { organizer: '#(organizer)', lao: '#(lao)', rollCall: '#(newRollCall)' } And def closeNewRollCall = newRollCall.close() diff --git a/tests/karate/src/test/java/be/features/rollCall/createRollCall.feature b/tests/karate/src/test/java/be/features/rollCall/createRollCall.feature index bf37bca415..680ad3706c 100644 --- a/tests/karate/src/test/java/be/features/rollCall/createRollCall.feature +++ b/tests/karate/src/test/java/be/features/rollCall/createRollCall.feature @@ -8,8 +8,8 @@ Feature: Create a Roll Call * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() - * def validRollCall = organizer.createValidRollCall(lao) + * def lao = organizer.generateValidLao() + * def validRollCall = organizer.generateValidRollCall(lao) # This call executes all the steps to create a valid lao on the server before every scenario # (lao creation, subscribe, catchup) @@ -213,8 +213,8 @@ Feature: Create a Roll Call # in an error message from the backend. @createRollCall9 Scenario: Roll Call Creation for non existent lao should return an error - Given def randomLao = organizer.createValidLao() - And def randomRollCall = organizer.createValidRollCall(randomLao) + Given def randomLao = organizer.generateValidLao() + And def randomRollCall = organizer.generateValidRollCall(randomLao) Given def validCreateRollCall = """ { diff --git a/tests/karate/src/test/java/be/features/rollCall/openRollCall.feature b/tests/karate/src/test/java/be/features/rollCall/openRollCall.feature index 61c46bc2b3..554e8099b8 100644 --- a/tests/karate/src/test/java/be/features/rollCall/openRollCall.feature +++ b/tests/karate/src/test/java/be/features/rollCall/openRollCall.feature @@ -8,8 +8,8 @@ Feature: Roll Call Open * call read(serverFeature) * call read(mockClientFeature) * def organizer = call createMockFrontend - * def lao = organizer.createValidLao() - * def rollCall = organizer.createValidRollCall(lao) + * def lao = organizer.generateValidLao() + * def rollCall = organizer.generateValidRollCall(lao) # This call executes all the steps to create a valid roll call on the server before every scenario # (lao creation, subscribe, catchup, roll call creation) @@ -56,7 +56,7 @@ Feature: Roll Call Open @openRollCall3 Scenario: Opening a Roll Call that was not created on the server returns an error - Given def newRollCall = organizer.createValidRollCall(lao) + Given def newRollCall = organizer.generateValidRollCall(lao) And def openNewRollCall = newRollCall.open() And def validOpenRollCall = """ diff --git a/tests/karate/src/test/java/be/features/utils/constants.feature b/tests/karate/src/test/java/be/features/utils/constants.feature index eec410170d..f9a3a47da6 100644 --- a/tests/karate/src/test/java/be/features/utils/constants.feature +++ b/tests/karate/src/test/java/be/features/utils/constants.feature @@ -11,7 +11,7 @@ Feature: Constants * def ELECTION_RESULTS = {"object": "election", "action": "result"} * def rootChannel = '/root' - * def random = Java.type('be.utils.RandomUtils') + * def random = Java.type('common.utils.RandomUtils') # Paths to util features * def utilsPath = 'classpath:be/features/utils/' diff --git a/tests/karate/src/test/java/be/features/utils/mockClient.feature b/tests/karate/src/test/java/be/features/utils/mockClient.feature index 8926ed4b3a..71badd5520 100644 --- a/tests/karate/src/test/java/be/features/utils/mockClient.feature +++ b/tests/karate/src/test/java/be/features/utils/mockClient.feature @@ -6,7 +6,7 @@ Feature: Mock Client * def createMockFrontend = """ function(){ - var MockClient = Java.type('be.utils.MockClient') + var MockClient = Java.type('common.utils.MockClient') var mockFrontend = new MockClient(frontendWsURL) mockClients.push(mockFrontend) return mockFrontend @@ -15,7 +15,7 @@ Feature: Mock Client * def createMockBackend = """ function(){ - var MockClient = Java.type('be.utils.MockClient') + var MockClient = Java.type('common.utils.MockClient') var mockBackend = new MockClient(backendWsURL) mockClients.push(mockBackend) return mockBackend diff --git a/tests/karate/src/test/java/be/utils/JsonConverter.java b/tests/karate/src/test/java/be/utils/JsonConverter.java index 65eb6d6c11..a2715fb059 100644 --- a/tests/karate/src/test/java/be/utils/JsonConverter.java +++ b/tests/karate/src/test/java/be/utils/JsonConverter.java @@ -4,6 +4,7 @@ import com.google.crypto.tink.subtle.Ed25519Sign; import com.intuit.karate.Json; import common.utils.Base64Utils; +import common.utils.Hash; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; diff --git a/tests/karate/src/test/java/be/utils/JsonConverterTest.java b/tests/karate/src/test/java/be/utils/JsonConverterTest.java index 549d47826d..c84219624f 100644 --- a/tests/karate/src/test/java/be/utils/JsonConverterTest.java +++ b/tests/karate/src/test/java/be/utils/JsonConverterTest.java @@ -2,6 +2,7 @@ import com.intuit.karate.Json; import common.utils.Base64Utils; +import common.utils.Hash; import org.junit.jupiter.api.Test; import java.security.GeneralSecurityException; diff --git a/tests/karate/src/test/java/be/model/Election.java b/tests/karate/src/test/java/common/model/Election.java similarity index 98% rename from tests/karate/src/test/java/be/model/Election.java rename to tests/karate/src/test/java/common/model/Election.java index e9c33aea16..812260d6cb 100644 --- a/tests/karate/src/test/java/be/model/Election.java +++ b/tests/karate/src/test/java/common/model/Election.java @@ -1,7 +1,7 @@ -package be.model; +package common.model; -import be.utils.Hash; -import be.utils.RandomUtils; +import common.utils.Hash; +import common.utils.RandomUtils; import java.time.Instant; import java.util.*; diff --git a/tests/karate/src/test/java/be/model/ElectionQuestion.java b/tests/karate/src/test/java/common/model/ElectionQuestion.java similarity index 98% rename from tests/karate/src/test/java/be/model/ElectionQuestion.java rename to tests/karate/src/test/java/common/model/ElectionQuestion.java index 1fc216f9e5..59fded6562 100644 --- a/tests/karate/src/test/java/be/model/ElectionQuestion.java +++ b/tests/karate/src/test/java/common/model/ElectionQuestion.java @@ -1,4 +1,4 @@ -package be.model; +package common.model; import java.util.List; diff --git a/tests/karate/src/test/java/be/model/KeyPair.java b/tests/karate/src/test/java/common/model/KeyPair.java similarity index 98% rename from tests/karate/src/test/java/be/model/KeyPair.java rename to tests/karate/src/test/java/common/model/KeyPair.java index 76d2b0f4f2..6f14895d68 100644 --- a/tests/karate/src/test/java/be/model/KeyPair.java +++ b/tests/karate/src/test/java/common/model/KeyPair.java @@ -1,4 +1,4 @@ -package be.model; +package common.model; import common.utils.Base64Utils; import karate.com.linecorp.armeria.internal.shaded.bouncycastle.crypto.params.Ed25519PrivateKeyParameters; diff --git a/tests/karate/src/test/java/be/model/Lao.java b/tests/karate/src/test/java/common/model/Lao.java similarity index 97% rename from tests/karate/src/test/java/be/model/Lao.java rename to tests/karate/src/test/java/common/model/Lao.java index 1dbaf10fb5..625de7ca2b 100644 --- a/tests/karate/src/test/java/be/model/Lao.java +++ b/tests/karate/src/test/java/common/model/Lao.java @@ -1,6 +1,6 @@ -package be.model; +package common.model; -import be.utils.Hash; +import common.utils.Hash; import java.util.ArrayList; import java.util.List; diff --git a/tests/karate/src/test/java/be/model/PlainVote.java b/tests/karate/src/test/java/common/model/PlainVote.java similarity index 96% rename from tests/karate/src/test/java/be/model/PlainVote.java rename to tests/karate/src/test/java/common/model/PlainVote.java index 12df731f1e..07a20c4647 100644 --- a/tests/karate/src/test/java/be/model/PlainVote.java +++ b/tests/karate/src/test/java/common/model/PlainVote.java @@ -1,4 +1,4 @@ -package be.model; +package common.model; /** Simplified version of a non-encrypted vote for one question, used to generate valid vote data. */ public class PlainVote { diff --git a/tests/karate/src/test/java/be/model/RollCall.java b/tests/karate/src/test/java/common/model/RollCall.java similarity index 99% rename from tests/karate/src/test/java/be/model/RollCall.java rename to tests/karate/src/test/java/common/model/RollCall.java index 0875ad7f2c..b7a1812e97 100644 --- a/tests/karate/src/test/java/be/model/RollCall.java +++ b/tests/karate/src/test/java/common/model/RollCall.java @@ -1,6 +1,6 @@ -package be.model; +package common.model; -import be.utils.Hash; +import common.utils.Hash; import java.time.Instant; import java.util.ArrayList; diff --git a/tests/karate/src/test/java/be/model/Transaction.java b/tests/karate/src/test/java/common/model/Transaction.java similarity index 99% rename from tests/karate/src/test/java/be/model/Transaction.java rename to tests/karate/src/test/java/common/model/Transaction.java index d2ff17d37d..a2a0981efa 100644 --- a/tests/karate/src/test/java/be/model/Transaction.java +++ b/tests/karate/src/test/java/common/model/Transaction.java @@ -1,6 +1,6 @@ -package be.model; +package common.model; -import be.utils.Hash; +import common.utils.Hash; import com.google.crypto.tink.PublicKeySign; import com.google.crypto.tink.subtle.Ed25519Sign; import common.utils.Base64Utils; diff --git a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java index 2a649ef688..a69df37257 100644 --- a/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java +++ b/tests/karate/src/test/java/common/net/MultiMsgWebSocketClient.java @@ -1,8 +1,8 @@ package common.net; import be.utils.JsonConverter; -import be.model.KeyPair; -import be.utils.RandomUtils; +import common.model.KeyPair; +import common.utils.RandomUtils; import com.intuit.karate.Json; import com.intuit.karate.Logger; import com.intuit.karate.http.WebSocketClient; diff --git a/tests/karate/src/test/java/be/utils/Hash.java b/tests/karate/src/test/java/common/utils/Hash.java similarity index 98% rename from tests/karate/src/test/java/be/utils/Hash.java rename to tests/karate/src/test/java/common/utils/Hash.java index 001c5fa5d9..46b73dd8e2 100644 --- a/tests/karate/src/test/java/be/utils/Hash.java +++ b/tests/karate/src/test/java/common/utils/Hash.java @@ -1,4 +1,4 @@ -package be.utils; +package common.utils; import common.utils.Base64Utils; diff --git a/tests/karate/src/test/java/be/utils/MockClient.java b/tests/karate/src/test/java/common/utils/MockClient.java similarity index 79% rename from tests/karate/src/test/java/be/utils/MockClient.java rename to tests/karate/src/test/java/common/utils/MockClient.java index e8b47e70c1..247c7a7c3f 100644 --- a/tests/karate/src/test/java/be/utils/MockClient.java +++ b/tests/karate/src/test/java/common/utils/MockClient.java @@ -1,6 +1,6 @@ -package be.utils; +package common.utils; -import be.model.*; +import common.model.*; import com.intuit.karate.http.WebSocketOptions; import com.intuit.karate.Logger; import common.net.MessageQueue; @@ -11,6 +11,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.HashMap; +import java.util.Map; /** Websocket client capable of creating lao, roll call, and election objects and issuing coins */ public class MockClient extends MultiMsgWebSocketClient { @@ -22,7 +24,7 @@ public MockClient(String wsURL) { /** * @return a valid lao with the client's public key, the current time, and a random valid lao name. */ - public Lao createValidLao() { + public Lao generateValidLao() { // Name needs to be random so that the same organizer does not create the same lao twice if it happens in the same second String randomName = RandomUtils.generateRandomName(); System.out.println("Client with public key: " + publicKey + " is creating a lao: " + randomName); @@ -33,7 +35,7 @@ public Lao createValidLao() { * @param lao the lao to create a roll call for * @return a valid roll call for the given lao */ - public RollCall createValidRollCall(Lao lao) { + public RollCall generateValidRollCall(Lao lao) { System.out.println("Client with public key: " + publicKey + " is creating roll call for lao: " + lao.id); long rollCallCreation = Instant.now().getEpochSecond(); // Name needs to be random so that the same organizer does not create the same roll call twice if it happens in the same second @@ -57,7 +59,7 @@ public RollCall createValidRollCall(Lao lao) { * @param lao the lao to create an election for * @return a valid empty election for the given lao, questions still need to be added! */ - public Election createValidElection(Lao lao) { + public Election generateValidElection(Lao lao) { System.out.println("Client with public key: " + publicKey + " is creating an election for lao: " + lao.id); long electionCreation = Instant.now().getEpochSecond(); // Name needs to be random so that the same organizer does not create the same election twice if it happens in the same second @@ -90,4 +92,34 @@ public Transaction issueCoins(MockClient receiver, long amountToGive) throws Gen transaction.issueInitialCoins(receiver.publicKey, publicKey, privateKey, amountToGive); return transaction; } + + /** + * Creates a newly generated lao. + * @return the lao created + */ + public Lao createLao() { + Lao lao = generateValidLao(); + return createLao(lao); + } + + /** + * Creates a lao. + * @param lao the lao to create + * @return the lao passed as argument + */ + public Lao createLao(Lao lao) { + Map request = new HashMap<>(); + request.put("object", "lao"); + request.put("action", "create"); + request.put("id", lao.id); + request.put("name", lao.name); + request.put("creation", lao.creation); + request.put("organizer", lao.organizerPk); + request.put("witnesses", lao.witnesses); + + this.publish(request, "/root"); + this.getBackendResponse(request); + + return lao; + } } diff --git a/tests/karate/src/test/java/be/utils/RandomUtils.java b/tests/karate/src/test/java/common/utils/RandomUtils.java similarity index 96% rename from tests/karate/src/test/java/be/utils/RandomUtils.java rename to tests/karate/src/test/java/common/utils/RandomUtils.java index e869ab6ef4..6eca0ac72d 100644 --- a/tests/karate/src/test/java/be/utils/RandomUtils.java +++ b/tests/karate/src/test/java/common/utils/RandomUtils.java @@ -1,10 +1,9 @@ -package be.utils; +package common.utils; -import be.model.Election; -import be.model.KeyPair; -import be.model.Lao; -import be.model.RollCall; -import common.utils.Base64Utils; +import common.model.Election; +import common.model.KeyPair; +import common.model.Lao; +import common.model.RollCall; import java.time.Instant; diff --git a/tests/karate/src/test/java/fe/features/lao.feature b/tests/karate/src/test/java/fe/features/lao.feature new file mode 100644 index 0000000000..20fc65cd3e --- /dev/null +++ b/tests/karate/src/test/java/fe/features/lao.feature @@ -0,0 +1,23 @@ +Feature: LAO + Background: + * call read('classpath:fe/utils/constants.feature') + * call read(MOCK_CLIENT_FEATURE) + + @name=lao_create + Scenario: Create a new LAO + Given call read(PLATFORM_FEATURE) { name: '#(CREATE_NEW_WALLET)' } + And def organization_name = 'My test organization' + When waitFor(lao_create_button).click() + And waitFor(lao_organization_name_input).input(organization_name) + And waitFor(lao_server_url_input).clear().input(serverURL) + And click(lao_launch_button) + Then waitFor(event_create_button) + Then screenshot() + + @name=lao_join + Scenario: Manually connect to an existing LAO + Given def organizer = createMockClient() + And def lao = organizer.createLao() + When call read(PLATFORM_FEATURE) { name: '#(JOIN_LAO)', params: { lao: '#(lao)' } } + Then assert !exists(event_create_button) + And screenshot() diff --git a/tests/karate/src/test/java/fe/features/wallet.feature b/tests/karate/src/test/java/fe/features/wallet.feature index c307741ec0..438f4c3465 100644 --- a/tests/karate/src/test/java/fe/features/wallet.feature +++ b/tests/karate/src/test/java/fe/features/wallet.feature @@ -1,8 +1,21 @@ Feature: Wallet Background: - * call read('classpath:fe/utils/constants.feature') + * call read('classpath:fe/utils/constants.feature') + @name=open_app Scenario: Open the app for the first time and see the wallet seed - When call read(PLATFORM_FEATURES) { name: "#(OPEN_APP)" } - Then match text(wallet_seed_wallet_text) == "#regex ^([a-z]+\\s){11}[a-z]+$" - And screenshot() + Given call read(PLATFORM_FEATURE) { name: "#(OPEN_APP)" } + When match text(wallet_seed_wallet_text) == "#regex ^([a-z]+\\s){11}[a-z]+$" + Then screenshot() + + @name=wallet_create + Scenario: Create a new wallet + Given call read(PLATFORM_FEATURE) { name: "#(CREATE_NEW_WALLET)" } + When waitFor(lao_join_button) + Then screenshot() + + @name=wallet_restore + Scenario: Restore a wallet + Given call read(PLATFORM_FEATURE) {name: "#(RESTORE_WALLET)", params: { seed: 'present guilt frost screen fabric rotate citizen decide have message chat hood' } } + When waitFor(lao_join_button) + Then screenshot() diff --git a/tests/karate/src/test/java/fe/utils/android.feature b/tests/karate/src/test/java/fe/utils/android.feature index 355e00f9fd..68fc6e7a89 100644 --- a/tests/karate/src/test/java/fe/utils/android.feature +++ b/tests/karate/src/test/java/fe/utils/android.feature @@ -4,8 +4,43 @@ Feature: android page object # Wallet screen * def wallet_button_empty_ok = '//*[@text="OK"]' * def wallet_seed_wallet_text = '#com.github.dedis.popstellar:id/seed_wallet_text' + * def wallet_new_wallet_button = "#com.github.dedis.popstellar:id/button_confirm_seed" + * def wallet_confirm_new_wallet_button = '//*[@text="YES"]' + * def wallet_restore_button = '#com.github.dedis.popstellar:id/import_seed_button' + * def wallet_restore_input = '#com.github.dedis.popstellar:id/import_seed_entry_edit_text' + + # Lao screen + * def lao_create_button = "#com.github.dedis.popstellar:id/home_create_button" + * def lao_join_button = '#com.github.dedis.popstellar:id/home_join_button' + * def lao_organization_name_input = "#com.github.dedis.popstellar:id/lao_name_entry_edit_text" + * def lao_server_url_input = "#com.github.dedis.popstellar:id/server_url_entry_edit_text" + * def lao_launch_button = "#com.github.dedis.popstellar:id/button_create" + * def lao_enter_manually_button = "#com.github.dedis.popstellar:id/scanner_enter_manually" + * def lao_enter_manually_lao_input = "#com.github.dedis.popstellar:id/manual_add_edit_text" + * def lao_enter_manually_submit_button = "#com.github.dedis.popstellar:id/manual_add_button" + + # Event screen + * def event_create_button = "#com.github.dedis.popstellar:id/add_event" @name=open_app Scenario: Given driver webDriverOptions - Then waitFor(wallet_button_empty_ok).click() + When waitFor(wallet_button_empty_ok).click() + + @name=create_new_wallet + Scenario: + Given call read('android.feature@name=open_app') + When waitFor(wallet_new_wallet_button) + And click(wallet_new_wallet_button) + And waitFor(wallet_confirm_new_wallet_button).click() + + @name=restore_wallet + Scenario: + Given call read('android.feature@name=open_app') + When input(wallet_restore_input, params.seed) + And click(wallet_restore_button) + + @name=lao_join + Scenario: + # Not implemented yet + * assert false diff --git a/tests/karate/src/test/java/fe/utils/constants.feature b/tests/karate/src/test/java/fe/utils/constants.feature index f78a8e0661..bae23bec27 100644 --- a/tests/karate/src/test/java/fe/utils/constants.feature +++ b/tests/karate/src/test/java/fe/utils/constants.feature @@ -1,5 +1,13 @@ @ignore @report=false Feature: Constants Scenario: Creates constants that will be used by other features - * def PLATFORM_FEATURES = 'classpath:fe/utils/platform.feature' + # Features + * def PLATFORM_FEATURE = 'classpath:fe/utils/platform.feature' + * def MOCK_CLIENT_FEATURE = 'classpath:fe/utils/mock_client.feature' + # Wallet * def OPEN_APP = 'open_app' + * def CREATE_NEW_WALLET = 'create_new_wallet' + * def RESTORE_WALLET = 'restore_wallet' + # Lao + * def JOIN_LAO = 'lao_join' + * def CREATE_LAO = 'lao_create' diff --git a/tests/karate/src/test/java/fe/utils/mock_client.feature b/tests/karate/src/test/java/fe/utils/mock_client.feature new file mode 100644 index 0000000000..8925b73db9 --- /dev/null +++ b/tests/karate/src/test/java/fe/utils/mock_client.feature @@ -0,0 +1,22 @@ +@ignore @report=false +Feature: Mock Client + + Scenario: Creates mock clients that can connect to a server as a frontend or server. Close the connections after each scenario. + * def mockClients = [] + * def createMockClient = + """ + function(){ + var MockClient = Java.type('common.utils.MockClient') + var mockFrontend = new MockClient(serverURL) + return mockFrontend + } + """ + * def stopAllMockClients = + """ + function(){ + for (var i = 0; i < mockClients.length; i++) { + mockClients[i].close(); + } + } + """ + * configure afterScenario = stopAllMockClients diff --git a/tests/karate/src/test/java/fe/utils/platform.feature b/tests/karate/src/test/java/fe/utils/platform.feature index b66e80a9df..4c29db95b0 100644 --- a/tests/karate/src/test/java/fe/utils/platform.feature +++ b/tests/karate/src/test/java/fe/utils/platform.feature @@ -4,4 +4,5 @@ Feature: current env page object * def page_object = 'classpath:fe/utils/.feature@name=' * replace page_object.env = karate.env * replace page_object.name = name - * call read(page_object) + * def params = karate.get('params', {}) + * call read(page_object) params diff --git a/tests/karate/src/test/java/fe/utils/web.feature b/tests/karate/src/test/java/fe/utils/web.feature index 0935e23a4e..3520c59a3f 100644 --- a/tests/karate/src/test/java/fe/utils/web.feature +++ b/tests/karate/src/test/java/fe/utils/web.feature @@ -1,13 +1,56 @@ @ignore @report=false Feature: web page object Background: + # Functions + # Wallet screen * def wallet_seed_wallet_text = "[data-testid='seed_wallet_text']" + * def wallet_new_wallet_button = "[data-testid='exploring_selector']" + * def wallet_goto_restore_wallet_button = "{}Restore" + * def wallet_restore_wallet_button = "{}Restore Wallet" + * def wallet_restore_input = "input" + + # Lao screen + * def lao_create_button = "{}Create" + * def lao_join_button = "{}Join" + * def lao_organization_name_input = "input[data-testid='launch_organization_name_selector']" + * def lao_server_url_input = "input[data-testid='launch_address_selector']" + * def lao_launch_button = "[data-testid='launch_launch_selector']" + * def lao_enter_manually_button = "{}Enter Manually" + * def lao_enter_manually_server_input = "input[placeholder='Server URI']" + * def lao_enter_manually_lao_input = "input[placeholder='LAO ID']" + * def lao_enter_manually_submit_button = "[data-testid='connect-button']" + + # Event screen + * def event_create_button = "[data-testid='create_event_selector']" + * def event_title = "{}Events" @name=open_app Scenario: Given driver webDriverOptions - Given driver 'about:blank' + And driver 'about:blank' And driver.dimensions = { left: 0, top: 0, width: screenWidth, height: screenHeight } - Then driver frontendURL - And delay(1000) + When driver frontendURL + + @name=create_new_wallet + Scenario: + Given call read('web.feature@name=open_app') + When waitFor(wallet_new_wallet_button) + And click(wallet_new_wallet_button) + + @name=restore_wallet + Scenario: + Given call read('web.feature@name=open_app') + When waitFor(wallet_goto_restore_wallet_button).click() + And input(wallet_restore_input, params.seed) + And click(wallet_restore_wallet_button) + + @name=lao_join + Scenario: + Given call read('web.feature@name=create_new_wallet') + When waitFor(lao_join_button).click() + And waitFor(lao_enter_manually_button).click() + And waitFor(lao_enter_manually_server_input).clear().input(serverURL) + And waitFor(lao_enter_manually_lao_input).clear().input(params.lao.id) + And click(lao_enter_manually_submit_button) + Then waitFor(event_title) diff --git a/tests/karate/src/test/java/karate-config.js b/tests/karate/src/test/java/karate-config.js index 65090d4374..ce370b0c96 100644 --- a/tests/karate/src/test/java/karate-config.js +++ b/tests/karate/src/test/java/karate-config.js @@ -33,6 +33,7 @@ function fn() { config.frontendWsURL = `ws://${config.host}:${config.frontendPort}/${config.frontendPath}`; config.backendWsURL = `ws://${config.host}:${config.backendPort}/${config.backendPath}`; } else if (env === 'web') { + config.serverURL = karate.properties['serverURL'] || 'ws://localhost:9000/client'; config.frontendURL = karate.properties['url'] || `file://${karate.toAbsolutePath('file:../../fe1-web/web-build/index.html')}`; config.screenWidth = karate.properties['screenWidth'] || 1920; config.screenHeight = karate.properties['screenHeight'] || 1080; @@ -97,6 +98,7 @@ function fn() { } }; } else if (env === 'android') { + config.serverURL = karate.properties['serverURL'] || 'ws://10.0.2.2:9000/client'; karate.configure('driver', { type: 'android', webDriverPath : "/", start: false }); const app = karate.properties['app'] || '../../fe2-android/app/build/outputs/apk/debug/app-debug.apk'; config.webDriverOptions = {