diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index ceb9740248..6a54b2cbf6 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -18,7 +18,10 @@ defaults: jobs: build: runs-on: ubuntu-latest - + defaults: + run: + working-directory: app/client + steps: # Checkout the code - uses: actions/checkout@v2 @@ -56,13 +59,149 @@ jobs: fi echo ::set-output name=REACT_APP_ENVIRONMENT::${REACT_APP_ENVIRONMENT} - - name: Build the code for automation - run: | - REACT_APP_ENVIRONMENT=${{steps.vars.outputs.REACT_APP_ENVIRONMENT}} GIT_SHA=${GITHUB_SHA} yarn build - - name: Run the jest tests run: REACT_APP_ENVIRONMENT=${{steps.vars.outputs.REACT_APP_ENVIRONMENT}} yarn run test:unit - + + - name: Create the bundle + run: REACT_APP_ENVIRONMENT=${{steps.vars.outputs.REACT_APP_ENVIRONMENT}} yarn build + + # Upload the build artifact so that it can be used by the test & deploy job in the workflow + - name: Upload react build bundle + uses: actions/upload-artifact@v2 + with: + name: build + path: app/client/build/ + + ui-test: + needs: build + runs-on: ubuntu-latest + # container: appsmith/cypress-nginx + defaults: + run: + working-directory: app/client + strategy: + fail-fast: false + matrix: + job: [0, 1, 2, 3, 4, 5, 6] + # Service containers to run with this job. Required for running tests + services: + # Label used to access the service container + redis: + # Docker Hub image for Redis + image: redis + ports: + # Opens tcp port 6379 on the host and service container + - 6379:6379 + mongo: + image: mongo + ports: + - 27017:27017 + + steps: + # Checkout the code + - uses: actions/checkout@v2 + + - name: Use Node.js 10.16.3 + uses: actions/setup-node@v1 + with: + node-version: '10.16.3' + + # Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again + - name: Cache npm dependencies + uses: actions/cache@v2 + env: + cache-name: cache-yarn-dependencies + with: + # maven dependencies are stored in `~/.m2` on Linux/macOS + path: ~/.npm + key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.OS }}-node- + ${{ runner.OS }}- + + # Install all the dependencies + - name: Install dependencies + run: yarn install + + - name: Download the react build artifact + uses: actions/download-artifact@v2 + with: + name: build + path: app/client/build + + - name: Pull server docker container and start it locally + shell: bash + run: | + echo ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin + docker run -d --net=host --name appsmith-internal-server -p 8080:8080 \ + --env APPSMITH_MONGODB_URI=mongodb://localhost:27017/appsmith \ + --env APPSMITH_REDIS_URL=redis://localhost:6379 \ + --env APPSMITH_ENCRYPTION_PASSWORD=password \ + --env APPSMITH_ENCRYPTION_SALT=salt \ + appsmith/appsmith-server:latest + + - name: Installing Yarn serve + run: | + yarn global add serve + echo "::add-path::$(yarn global bin)" + + - name: Setting up the cypress tests + shell: bash + env: + APPSMITH_SSL_CERTIFICATE: ${{ secrets.APPSMITH_SSL_CERTIFICATE }} + APPSMITH_SSL_KEY: ${{ secrets.APPSMITH_SSL_KEY }} + CYPRESS_URL: ${{ secrets.CYPRESS_URL }} + CYPRESS_USERNAME: ${{ secrets.CYPRESS_USERNAME }} + CYPRESS_PASSWORD: ${{ secrets.CYPRESS_PASSWORD }} + run: | + ./cypress/setup-test.sh + + - name: Run the cypress test + uses: cypress-io/github-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + CYPRESS_USERNAME: ${{ secrets.CYPRESS_USERNAME }} + CYPRESS_PASSWORD: ${{ secrets.CYPRESS_PASSWORD }} + with: + browser: chrome + headless: true + record: true + install: false + parallel: true + group: 'Electrons on Github Action' + ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' + spec: 'cypress/integration/Smoke_TestSuite/*/*' + working-directory: app/client + + # Upload the screenshots as artifacts if there's a failure + - uses: actions/upload-artifact@v1 + if: failure() + with: + name: cypress-screenshots-${{ matrix.job }} + path: app/client/cypress/screenshots/ + + package: + needs: ui-test + runs-on: ubuntu-latest + defaults: + run: + working-directory: app/client + # Run this job only if all the previous steps are a success and the reference if the release or master branch + if: always() && (github.ref == 'refs/heads/release' || github.ref == 'refs/heads/release') + + steps: + + # Checkout the code + - uses: actions/checkout@v2 + + - name: Download the react build artifact + uses: actions/download-artifact@v2 + with: + name: build + path: app/client/build + # Here, the GITHUB_REF is of type /refs/head/. We extract branch_name from this by removing the # first 11 characters. This can be used to build images for several branches - name: Get the version to tag the Docker image diff --git a/app/client/Dockerfile-cypress-test b/app/client/Dockerfile-cypress-test index 7ecc32f8ae..be82fbacee 100644 --- a/app/client/Dockerfile-cypress-test +++ b/app/client/Dockerfile-cypress-test @@ -1,3 +1,8 @@ -FROM cypress/browsers:node10.16.3-chrome80-ff73 +# FROM cypress/browsers:node10.16.3-chrome80-ff73 +FROM nginx:1.17.9-alpine -RUN apt-get update -y && apt-get install -y nginx && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && yarn global add serve +RUN apt update -y -q && \ + apt-get install -y -q nginx gettext-base && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ + yarn global add serve diff --git a/app/client/README.md b/app/client/README.md index 80e0089a32..dbbdec4dc2 100755 --- a/app/client/README.md +++ b/app/client/README.md @@ -79,3 +79,5 @@ This section has moved here: https://facebook.github.io/create-react-app/docs/de ### `npm run build` fails to minify This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify + + diff --git a/app/client/cypress.json b/app/client/cypress.json index 0a93601d47..e1a813ea99 100644 --- a/app/client/cypress.json +++ b/app/client/cypress.json @@ -12,9 +12,5 @@ "json": false }, "viewportHeight": 900, - "viewportWidth": 1400, - "env": { - "username": "", - "password": "" - } + "viewportWidth": 1400 } diff --git a/app/client/cypress/cypress-docker-compose.yml b/app/client/cypress/cypress-docker-compose.yml new file mode 100644 index 0000000000..9a566af92d --- /dev/null +++ b/app/client/cypress/cypress-docker-compose.yml @@ -0,0 +1,33 @@ +version: "3.7" + +services: + appsmith-server: + image: appsmith/appsmith-server:latest + environment: + APPSMITH_MONGODB_URI: "mongodb://mongo:27017/appsmith" + APPSMITH_REDIS_URL: "redis://redis:6379" + APPSMITH_MAIL_ENABLED: "false" + ports: + - "8080:8080" + links: + - mongo + depends_on: + - mongo + networks: + - appsmith + + mongo: + image: mongo + environment: + - MONGO_INITDB_DATABASE=appsmith + networks: + - appsmith + + redis: + image: redis + networks: + - appsmith + +networks: + appsmith: + driver: bridge \ No newline at end of file diff --git a/app/client/cypress/fixtures/inputdata.json b/app/client/cypress/fixtures/inputdata.json deleted file mode 100644 index ba88bf9071..0000000000 --- a/app/client/cypress/fixtures/inputdata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "appname": "AutoDslCypress" - -} \ No newline at end of file diff --git a/app/client/cypress/fixtures/propertyPaneResponse.json b/app/client/cypress/fixtures/propertyPaneResponse.json new file mode 100644 index 0000000000..84aa6a57d9 --- /dev/null +++ b/app/client/cypress/fixtures/propertyPaneResponse.json @@ -0,0 +1,1135 @@ +{ + "responseMeta": { + "status": 200, + "success": true + }, + "data": { + "id": "5dcceb61ae82b00004d7b2fe", + "userPermissions": [], + "config": { + "CONTAINER_WIDGET": [ + { + "id": "5.1", + "sectionName": "General", + "children": [ + { + "id": "5.1.1", + "helpText": "Use a html color name, HEX, RGB or RGBA value", + "placeholderText": "#FFFFFF / Gray / rgb(255, 99, 71)", + "propertyName": "backgroundColor", + "label": "Background Color", + "controlType": "INPUT_TEXT" + }, + { + "id": "5.1.2", + "helpText": "Controls the visibility of the widget", + "propertyName": "isVisible", + "label": "Visible", + "controlType": "SWITCH", + "isJSConvertible": true + }, + { + "id": "5.1.3", + "propertyName": "shouldScrollContents", + "label": "Scroll Contents", + "controlType": "SWITCH" + } + ] + } + ], + "DATE_PICKER_WIDGET": [ + { + "sectionName": "General", + "id": "6.1", + "children": [ + { + "id": "6.1.2", + "propertyName": "defaultDate", + "label": "Default Date", + "helpText": "Sets the default date of the widget. The date is updated if the default date changes", + "controlType": "DATE_PICKER", + "placeholderText": "Enter Default Date", + "isJSConvertible": true + }, + { + "id": "6.1.3", + "helpText": "Sets the format of the selected date", + "propertyName": "dateFormat", + "label": "Date Format", + "controlType": "DROP_DOWN", + "isJSConvertible": true, + "options": [ + { + "label": "YYYY-MM-DD", + "value": "YYYY-MM-DD" + }, + { + "label": "YYYY-MM-DD HH:mm", + "value": "YYYY-MM-DD HH:mm" + }, + { + "label": "YYYY-MM-DDTHH:mm:ss.sssZ", + "value": "YYYY-MM-DDTHH:mm:ss.sssZ" + }, + { + "label": "DD/MM/YYYY", + "value": "DD/MM/YYYY" + }, + { + "label": "DD/MM/YYYY HH:mm", + "value": "DD/MM/YYYY HH:mm" + } + ] + }, + { + "id": "6.1.4", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH" + }, + { + "id": "6.1.5", + "propertyName": "isDisabled", + "label": "Disabled", + "helpText": "Disables input to this widget", + "controlType": "SWITCH" + } + ] + }, + { + "sectionName": "Actions", + "id": "6.2", + "children": [ + { + "id": "6.2.1", + "propertyName": "onDateSelected", + "label": "onDateSelected", + "controlType": "ACTION_SELECTOR" + } + ] + } + ], + "TABLE_WIDGET": [ + { + "id": "7.1", + "sectionName": "General", + "children": [ + { + "id": "7.1.1", + "helpText": "Takes in an array of objects to display rows in the table. Bind data from an API using {{}}", + "propertyName": "tableData", + "label": "Table Data", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter [{ \"col1\": \"val1\" }]", + "inputType": "ARRAY" + }, + { + "id": "7.1.2", + "helpText": "Bind the Table.pageNo property in your API and call it onPageChange", + "propertyName": "serverSidePaginationEnabled", + "label": "Server Side Pagination", + "controlType": "SWITCH" + }, + { + "id": "7.1.3", + "helpText": "Controls the visibility of the widget", + "propertyName": "isVisible", + "isJSConvertible": true, + "label": "Visible", + "controlType": "SWITCH" + }, + { + "id": "7.1.4", + "helpText": "Enable PDF Export", + "propertyName": "exportPDF", + "label": "PDF Export", + "controlType": "SWITCH" + }, + { + "id": "7.1.5", + "helpText": "Enable Excel Export", + "propertyName": "exportExcel", + "label": "Excel Export", + "controlType": "SWITCH" + }, + { + "id": "7.1.6", + "helpText": "Enable CSV Export", + "propertyName": "exportCsv", + "label": "CSV Export", + "controlType": "SWITCH" + } + ] + }, + { + "id": "7.2", + "sectionName": "Actions", + "children": [ + { + "id": "7.2.1", + "helpText": "Adds a button action for every row. Reference the Table.selectedRow property in the action", + "propertyName": "columnActions", + "label": "Row Button", + "controlType": "COLUMN_ACTION_SELECTOR" + }, + { + "id": "7.2.2", + "helpText": "Triggers an action when a table row is selected", + "propertyName": "onRowSelected", + "label": "onRowSelected", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + }, + { + "id": "7.2.3", + "helpText": "Triggers an action when a table page is changed", + "propertyName": "onPageChange", + "label": "onPageChange", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "IMAGE_WIDGET": [ + { + "id": "3.1", + "sectionName": "General", + "children": [ + { + "id": "3.1.1", + "helpText": "Renders the url or Base64 in the widget", + "propertyName": "image", + "label": "Image", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter URL / Base64" + }, + { + "id": "3.1.2", + "helpText": "Renders the url or Base64 when no image is provided", + "propertyName": "defaultImage", + "label": "Default Image", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter URL / Base64" + }, + { + "id": "3.1.3", + "helpText": "Controls the visibility of the widget", + "propertyName": "isVisible", + "label": "Visible", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + } + ], + "RADIO_GROUP_WIDGET": [ + { + "id": "10.1", + "sectionName": "General", + "children": [ + { + "id": "10.1.2", + "helpText": "Displays a list of options for a user to select. Values must be unique", + "propertyName": "options", + "label": "Options", + "controlType": "OPTION_INPUT", + "isJSConvertible": true + }, + { + "id": "10.1.3", + "helpText": "Selects a value of the options entered by default", + "propertyName": "defaultOptionValue", + "label": "Default Selected Value", + "placeholderText": "Enter option value", + "controlType": "INPUT_TEXT" + }, + { + "id": "10.1.5", + "helpText": "Controls the visibility of the widget", + "propertyName": "isVisible", + "label": "Visible", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + }, + { + "id": "10.2", + "sectionName": "Actions", + "children": [ + { + "id": "10.2.1", + "helpText": "Triggers an action when a user changes the selected option", + "propertyName": "onSelectionChange", + "label": "onSelectionChange", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "TABS_WIDGET": [ + { + "id": "16.1", + "sectionName": "General", + "children": [ + { + "id": "16.1.1", + "helpText": "Takes an array of tab names to render tabs", + "propertyName": "tabs", + "isJSConvertible": true, + "label": "Tabs", + "controlType": "TABS_INPUT" + }, + { + "id": "16.1.2", + "propertyName": "selectedTab", + "helpText": "Selects a tab name specified by default", + "placeholderText": "Enter tab name", + "label": "Default Tab", + "controlType": "INPUT_TEXT" + }, + { + "id": "16.1.5", + "propertyName": "shouldScrollContents", + "label": "Scroll Contents", + "controlType": "SWITCH" + }, + { + "id": "16.1.4", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + } + ], + "CHART_WIDGET": [ + { + "id": "13.1", + "sectionName": "General", + "children": [ + { + "id": "13.1.1", + "helpText": "Adds a title to the chart", + "placeholderText": "Enter title", + "propertyName": "chartName", + "label": "Title", + "controlType": "INPUT_TEXT" + }, + { + "id": "13.1.2", + "helpText": "Changes the visualisation of the chart data", + "propertyName": "chartType", + "label": "Chart Type", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Line Chart", + "value": "LINE_CHART" + }, + { + "label": "Bar Chart", + "value": "BAR_CHART" + }, + { + "label": "Pie Chart", + "value": "PIE_CHART" + }, + { + "label": "Column Chart", + "value": "COLUMN_CHART" + }, + { + "label": "Area Chart", + "value": "AREA_CHART" + } + ], + "isJSConvertible": true + }, + { + "id": "13.1.62", + "helpText": "Populates the chart with the data", + "propertyName": "chartData", + "placeholderText": "Enter [{ \"x\": \"val\", \"y\": \"val\" }]", + "label": "Chart Data", + "controlType": "CHART_DATA" + }, + { + "id": "13.1.3", + "helpText": "Specifies the label of the x-axis", + "propertyName": "xAxisName", + "placeholderText": "Enter label text", + "label": "x-axis Label", + "controlType": "INPUT_TEXT" + }, + { + "id": "13.1.5", + "helpText": "Specifies the label of the y-axis", + "propertyName": "yAxisName", + "placeholderText": "Enter label text", + "label": "y-axis Label", + "controlType": "INPUT_TEXT" + }, + { + "id": "13.1.4", + "helpText": "Enables scrolling inside the chart", + "propertyName": "allowHorizontalScroll", + "label": "Allow horizontal scroll", + "controlType": "SWITCH" + }, + { + "id": "13.1.7", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + } + ], + "MODAL_WIDGET": [ + { + "sectionName": "General", + "id": "18.1", + "children": [ + { + "id": "18.1.1", + "propertyName": "canOutsideClickClose", + "label": "Quick Dismiss", + "helpText": "Allows dismissing the modal when user taps outside", + "controlType": "SWITCH" + }, + { + "id": "18.1.2", + "propertyName": "size", + "label": "Modal Type", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Form Modal", + "value": "MODAL_LARGE" + }, + { + "label": "Alert Modal", + "value": "MODAL_SMALL" + } + ] + }, + { + "id": "18.1.3", + "propertyName": "shouldScrollContents", + "label": "Scroll Contents", + "controlType": "SWITCH" + } + ] + } + ], + "INPUT_WIDGET": [ + { + "id": "4.1", + "sectionName": "General", + "children": [ + { + "id": "4.1.2", + "helpText": "Changes the type of data captured in the input", + "propertyName": "inputType", + "label": "Data Type", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Text", + "value": "TEXT" + }, + { + "label": "Number", + "value": "NUMBER" + }, + { + "label": "Password", + "value": "PASSWORD" + }, + { + "label": "Phone Number", + "value": "PHONE_NUMBER" + }, + { + "label": "Email", + "value": "EMAIL" + } + ] + }, + { + "id": "4.1.3", + "helpText": "Sets a placeholder text for the input", + "propertyName": "placeholderText", + "label": "Placeholder", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter placeholder text" + }, + { + "id": "4.1.4", + "helpText": "Sets the default text of the widget. The text is updated if the default text changes", + "propertyName": "defaultText", + "label": "Default Input", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter default text" + }, + { + "id": "4.1.5", + "helpText": "Adds a validation to the input which displays an error on failure", + "propertyName": "regex", + "label": "Regex", + "controlType": "INPUT_TEXT", + "placeholderText": "^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$", + "inputType": "TEXT" + }, + { + "id": "4.1.6", + "helpText": "Displays the error message if the regex validation fails", + "propertyName": "errorMessage", + "label": "Error Message", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter error message", + "inputType": "TEXT" + }, + { + "id": "4.1.8", + "helpText": "Controls the visibility of the widget", + "propertyName": "isVisible", + "label": "Visible", + "controlType": "SWITCH", + "isJSConvertible": true + }, + { + "id": "4.1.9", + "helpText": "Disables input to this widget", + "propertyName": "isDisabled", + "label": "Disabled", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + }, + { + "id": "4.2.1", + "sectionName": "Actions", + "children": [ + { + "id": "5.11.2", + "helpText": "Triggers an action when the text is changed", + "propertyName": "onTextChanged", + "label": "onTextChanged", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "DROP_DOWN_WIDGET": [ + { + "id": "8.1", + "sectionName": "General", + "children": [ + { + "id": "8.1.2", + "helpText": "Allows users to select either a single option or multiple options", + "propertyName": "selectionType", + "label": "Selection Type", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Single Select", + "value": "SINGLE_SELECT" + }, + { + "label": "Multi Select", + "value": "MULTI_SELECT" + } + ] + }, + { + "id": "8.1.3", + "helpText": "Allows users to select either a single option or multiple options. Values must be unique", + "propertyName": "options", + "label": "Options", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter [{label: \"label1\", value: \"value2\"}]" + }, + { + "id": "8.1.4", + "helpText": "Selects the option with value by default", + "propertyName": "defaultOptionValue", + "label": "Default Option", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter option value" + }, + { + "id": "8.1.6", + "helpText": "Controls the visibility of the widget", + "propertyName": "isVisible", + "label": "Visible", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + }, + { + "id": "8.2", + "sectionName": "Actions", + "children": [ + { + "id": "8.2.1", + "helpText": "Triggers an action when a user selects an option", + "propertyName": "onOptionChange", + "label": "onOptionChange", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "FORM_BUTTON_WIDGET": [ + { + "id": "15.1", + "sectionName": "General", + "children": [ + { + "id": "15.1.1", + "propertyName": "text", + "label": "Label", + "helpText": "Sets the label of the button", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter label text" + }, + { + "id": "15.1.2", + "propertyName": "buttonStyle", + "label": "Button Style", + "helpText": "Changes the style of the button", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Primary Button", + "value": "PRIMARY_BUTTON" + }, + { + "label": "Secondary Button", + "value": "SECONDARY_BUTTON" + }, + { + "label": "Danger Button", + "value": "DANGER_BUTTON" + } + ] + }, + { + "id": "15.1.3", + "helpText": "Disables the button when the parent form has a required widget that is not filled", + "propertyName": "disabledWhenInvalid", + "label": "Disabled Invalid Forms", + "controlType": "SWITCH" + }, + { + "id": "15.1.4", + "helpText": "Resets the fields within the parent form when the click action succeeds", + "propertyName": "resetFormOnClick", + "label": "Reset Form on Success", + "controlType": "SWITCH", + "isJSConvertible": true + }, + { + "id": "15.1.5", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + }, + { + "id": "15.2", + "sectionName": "Actions", + "children": [ + { + "id": "15.2.1", + "helpText": "Triggers an action when the button is clicked", + "propertyName": "onClick", + "label": "onClick", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "MAP_WIDGET": [ + { + "sectionName": "General", + "id": "25.1", + "children": [ + { + "id": "25.1.1", + "propertyName": "mapCenter", + "label": "Initial location", + "isJSConvertible": true, + "controlType": "LOCATION_SEARCH" + }, + { + "id": "25.1.4", + "propertyName": "defaultMarkers", + "label": "Default markers", + "controlType": "INPUT_TEXT", + "inputType": "ARRAY", + "helpText": "Sets the default markers on the map", + "placeholderText": "Enter [{ \"lat\": \"val1\", \"long\": \"val2\" }]" + }, + { + "id": "25.1.2", + "propertyName": "enableSearch", + "label": "Enable search location", + "helpText": "Enables locaton search", + "controlType": "SWITCH" + }, + { + "id": "25.1.3", + "propertyName": "enablePickLocation", + "label": "Enable pick location", + "helpText": "Allows a user to pick their location", + "controlType": "SWITCH" + }, + { + "id": "25.1.5", + "propertyName": "enableCreateMarker", + "label": "Create new marker", + "helpText": "Allows users to mark locations on the map", + "controlType": "SWITCH" + }, + { + "id": "25.1.6", + "propertyName": "zoomLevel", + "label": "Zoom Level", + "controlType": "STEP", + "helpText": "Changes the default zoom of the map", + "stepType": "ZOOM_PERCENTAGE" + }, + { + "id": "25.1.7", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + }, + { + "id": "27", + "sectionName": "Actions", + "children": [ + { + "id": "27.1", + "propertyName": "onMarkerClick", + "label": "onMarkerClick", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + }, + { + "id": "27.2", + "propertyName": "onCreateMarker", + "label": "onCreateMarker", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "BUTTON_WIDGET": [ + { + "id": "1.1", + "sectionName": "General", + "children": [ + { + "id": "1.1.1", + "propertyName": "text", + "label": "Label", + "helpText": "Sets the label of the button", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter label text" + }, + { + "id": "1.1.2", + "propertyName": "buttonStyle", + "label": "Button Style", + "controlType": "DROP_DOWN", + "helpText": "Changes the style of the button", + "options": [ + { + "label": "Primary Button", + "value": "PRIMARY_BUTTON" + }, + { + "label": "Secondary Button", + "value": "SECONDARY_BUTTON" + }, + { + "label": "Danger Button", + "value": "DANGER_BUTTON" + } + ] + }, + { + "id": "1.1.3", + "propertyName": "isDisabled", + "label": "Disabled", + "controlType": "SWITCH", + "helpText": "Disables clicks to this widget", + "isJSConvertible": true + }, + { + "id": "1.1.4", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + }, + { + "id": "1.2.1", + "sectionName": "Actions", + "children": [ + { + "id": "2.1", + "helpText": "Triggers an action when the button is clicked", + "propertyName": "onClick", + "label": "onClick", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "RICH_TEXT_EDITOR_WIDGET": [ + { + "id": "12.1", + "sectionName": "General", + "children": [ + { + "id": "12.1.1", + "propertyName": "defaultText", + "helpText": "Sets the default text of the widget. The text is updated if the default text changes", + "label": "Default text", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter HTML" + }, + { + "id": "12.1.2", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH", + "isJSConvertible": true + }, + { + "id": "12.1.3", + "propertyName": "isDisabled", + "label": "Disable", + "helpText": "Disables input to this widget", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + }, + { + "id": "12.2", + "sectionName": "Actions", + "children": [ + { + "id": "12.2.1", + "helpText": "Triggers an action when the text is changed", + "propertyName": "onTextChange", + "label": "onTextChange", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "FILE_PICKER_WIDGET": [ + { + "id": "11.1", + "sectionName": "General", + "children": [ + { + "id": "11.1.1", + "propertyName": "label", + "label": "Label", + "controlType": "INPUT_TEXT", + "helpText": "Sets the label of the button", + "placeholderText": "Enter label text", + "inputType": "TEXT" + }, + { + "id": "11.1.2", + "propertyName": "maxNumFiles", + "label": "Max No. files", + "helpText": "Sets the maximum number of files that can be uploaded at once", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter no. of files", + "inputType": "INTEGER" + }, + { + "id": "11.1.3", + "propertyName": "maxFileSize", + "helpText": "Sets the maximum size of each file that can be uploaded", + "label": "Max file size", + "controlType": "INPUT_TEXT", + "placeholderText": "File size in mb", + "inputType": "INTEGER" + }, + { + "id": "11.1.4", + "propertyName": "allowedFileTypes", + "helpText": "Restricts the type of files which can be uploaded", + "label": "Allowed File Types", + "controlType": "MULTI_SELECT", + "placeholderText": "Select file types", + "options": [ + { + "label": "Any File", + "value": "*" + }, + { + "label": "Images", + "value": "image/*" + }, + { + "label": "Videos", + "value": "video/*" + }, + { + "label": "Audio", + "value": "audio/*" + }, + { + "label": "Text", + "value": "text/*" + }, + { + "label": "MS Word", + "value": ".doc" + }, + { + "label": "JPEG", + "value": "image/jpeg" + }, + { + "label": "PNG", + "value": ".png" + } + ], + "isJSConvertible": true + }, + { + "id": "11.1.6", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH", + "isJSConvertible": true + }, + { + "id": "11.1.7", + "propertyName": "uploadedFileUrlPaths", + "helpText": "Stores the url of the uploaded file so that it can be referenced in an action later", + "label": "Uploaded File URLs", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter [ \"url1\", \"url2\" ]", + "inputType": "TEXT" + } + ] + }, + { + "id": "11.2", + "sectionName": "Actions", + "children": [ + { + "id": "11.2.1", + "helpText": "Triggers an action when the user selects a file. Upload files to a CDN here and store their urls in uploadedFileUrls", + "propertyName": "onFilesSelected", + "label": "onFilesSelected", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "CHECKBOX_WIDGET": [ + { + "id": "9.1", + "sectionName": "General", + "children": [ + { + "id": "9.1.1", + "propertyName": "label", + "label": "Label", + "controlType": "INPUT_TEXT", + "helpText": "Displays a label next to the widget", + "placeholderText": "Enter label text" + }, + { + "id": "9.1.2", + "propertyName": "defaultCheckedState", + "label": "Default Selected", + "helpText": "Checks / un-checks the checkbox by default. Changes to the default selection update the widget state", + "controlType": "SWITCH", + "isJSConvertible": true + }, + { + "id": "9.1.4", + "propertyName": "isDisabled", + "label": "Disabled", + "controlType": "SWITCH", + "helpText": "Disables input to this widget", + "isJSConvertible": true + }, + { + "id": "9.1.5", + "propertyName": "isVisible", + "label": "Visible", + "helpText": "Controls the visibility of the widget", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + }, + { + "id": "9.2", + "sectionName": "Actions", + "children": [ + { + "id": "9.2.1", + "helpText": "Triggers an action when the check state is changed", + "propertyName": "onCheckChange", + "label": "onCheckChange", + "controlType": "ACTION_SELECTOR", + "isJSConvertible": true + } + ] + } + ], + "FORM_WIDGET": [ + { + "id": "14.1", + "sectionName": "General", + "children": [ + { + "id": "14.1.1", + "propertyName": "backgroundColor", + "label": "Background Color", + "helpText": "Use a html color name, HEX, RGB or RGBA value", + "placeholderText": "#FFFFFF / Gray / rgb(255, 99, 71)", + "controlType": "INPUT_TEXT" + }, + { + "id": "14.1.2", + "helpText": "Controls the visibility of the widget", + "propertyName": "isVisible", + "label": "Visible", + "controlType": "SWITCH", + "isJSConvertible": true + }, + { + "id": "14.1.3", + "propertyName": "shouldScrollContents", + "label": "Scroll Contents", + "controlType": "SWITCH" + } + ] + } + ], + "TEXT_WIDGET": [ + { + "id": "2.1", + "sectionName": "General", + "children": [ + { + "id": "2.1.1", + "propertyName": "text", + "helpText": "Sets the text of the widget", + "label": "Text", + "controlType": "INPUT_TEXT", + "placeholderText": "Enter text" + }, + { + "id": "2.1.3", + "propertyName": "textAlign", + "helpText": "Sets the alignments of the text", + "label": "Text Align", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Left", + "value": "LEFT" + }, + { + "label": "Center", + "value": "CENTER" + }, + { + "label": "Right", + "value": "RIGHT" + } + ] + }, + { + "id": "2.1.2", + "propertyName": "textStyle", + "helpText": "Sets the font and style of the text", + "label": "Text Style", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Heading", + "value": "HEADING" + }, + { + "label": "Label", + "value": "LABEL" + }, + { + "label": "Body", + "value": "BODY" + } + ] + }, + { + "id": "2.1.3", + "propertyName": "shouldScroll", + "label": "Enable Scroll", + "helpText": "Allows scrolling text instead of truncation", + "controlType": "SWITCH" + }, + { + "id": "2.1.4", + "propertyName": "isVisible", + "helpText": "Controls the visibility of the widget", + "label": "Visible", + "controlType": "SWITCH", + "isJSConvertible": true + } + ] + } + ] + }, + "name": "propertyPane", + "new": false + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/user.json b/app/client/cypress/fixtures/user.json deleted file mode 100644 index 7d95e02603..0000000000 --- a/app/client/cypress/fixtures/user.json +++ /dev/null @@ -1,6 +0,0 @@ - - { - "username": "testowner@appsmith.com", - "password": "own3rT3st1ng" - - } \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/DynamicInput/autocomplete_spec.js b/app/client/cypress/integration/Smoke_TestSuite/DynamicInput/autocomplete_spec.js index 359af1807a..7090e3690c 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/DynamicInput/autocomplete_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/DynamicInput/autocomplete_spec.js @@ -1,3 +1,5 @@ +/// + const dsl = require("../../../fixtures/commondsl.json"); const pages = require("../../../locators/Pages.json"); const dynamicInputLocators = require("../../../locators/DynamicInput.json"); @@ -8,6 +10,7 @@ describe("Dynamic input autocomplete", () => { cy.addDsl(dsl); }); it("opens autocomplete for bindings", () => { + cy.wait("@getPropertyPane"); cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("buttonwidget"); cy.get(dynamicInputLocators.input) @@ -55,6 +58,7 @@ describe("Dynamic input autocomplete", () => { }); it("opens current value popup", () => { // Test on widgets pane + cy.wait("@getPropertyPane"); cy.get(pages.widgetsEditor).click(); cy.openPropertyPane("buttonwidget"); cy.get(dynamicInputLocators.input) diff --git a/app/client/cypress/integration/Smoke_TestSuite/UnitTest/LoginFromUIApp_spec.js b/app/client/cypress/integration/Smoke_TestSuite/UnitTest/LoginFromUIApp_spec.js index 7e177c60b3..104ad5b4e6 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/UnitTest/LoginFromUIApp_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/UnitTest/LoginFromUIApp_spec.js @@ -1,11 +1,10 @@ -const loginData = require("../../../fixtures/user.json"); let pageid; let appId; describe("Login from UI and check the functionality", function() { it("Login/create page/delete page/delete app from UI", function() { const appname = localStorage.getItem("AppName"); - cy.LogintoApp(loginData.username, loginData.password); + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); cy.SearchApp(appname); cy.get("#loading").should("not.exist"); cy.wait("@getPropertyPane"); diff --git a/app/client/cypress/plugins/index.js b/app/client/cypress/plugins/index.js index f1a773ce0d..ba7c6b9720 100644 --- a/app/client/cypress/plugins/index.js +++ b/app/client/cypress/plugins/index.js @@ -26,4 +26,16 @@ module.exports = (on, config) => { return launchOptions; }); + + /** + * This task logs the message on the CLI terminal. Use with care because it can log sensitive details + * Example usage: cy.task('log', 'This is the message printed to the terminal') + */ + + on("task", { + log(message) { + console.log(message); + return null; + }, + }); }; diff --git a/app/client/cypress/setup-test.sh b/app/client/cypress/setup-test.sh new file mode 100755 index 0000000000..3325d03718 --- /dev/null +++ b/app/client/cypress/setup-test.sh @@ -0,0 +1,46 @@ +#! /bin/sh + +# This script is responsible for setting up the local Nginx server for running E2E Cypress tests +# on our CI/CD system. Currently the script is geared towards Github Actions + +# Serve the react bundle on a specific port. Nginx will proxy to this port +echo "Starting the setup the test framework" +sudo echo "127.0.0.1 dev.appsmith.com" | sudo tee -a /etc/hosts +serve -s build -p 3000 & + +# Substitute all the env variables in nginx +vars_to_substitute=$(printf '\$%s,' $(env | grep -o "^APPSMITH_[A-Z0-9_]\+" | xargs)) +cat ./docker/templates/nginx-linux.conf.template | envsubst ${vars_to_substitute} | sed -e 's|\${\(APPSMITH_[A-Z0-9_]*\)}||g' > ./docker/nginx.conf + +# Create the SSL files for Nginx. Required for service workers to work properly. +touch ./docker/dev.appsmith.com.pem ./docker/dev.appsmith.com-key.pem +echo "$APPSMITH_SSL_CERTIFICATE" > ./docker/dev.appsmith.com.pem +echo "$APPSMITH_SSL_KEY" > ./docker/dev.appsmith.com-key.pem + +echo "Going to run the nginx server" +sudo docker pull nginx:latest + +sudo docker run --network host --name wildcard-nginx -d -p 80:80 -p 443:443 \ + -v `pwd`/docker/nginx.conf:/etc/nginx/conf.d/app.conf \ + -v `pwd`/docker/dev.appsmith.com.pem:/etc/certificate/dev.appsmith.com.pem \ + -v `pwd`/docker/dev.appsmith.com-key.pem:/etc/certificate/dev.appsmith.com-key.pem \ + nginx:latest & + +echo "Sleeping for 10 seconds to let the server start" +sleep 10 + +# Create the test user +curl -k --request POST -v 'https://dev.appsmith.com/api/v1/users' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "name" : "'"$CYPRESS_USERNAME"'", + "email" : "'"$CYPRESS_USERNAME"'", + "source" : "FORM", + "state" : "ACTIVATED", + "isEnabled" : "true", + "password": "'"$CYPRESS_PASSWORD"'" +}' + +# DEBUG=cypress:* $(npm bin)/cypress version +# sed -i -e "s|api_url:.*$|api_url: $CYPRESS_URL|g" /github/home/.cache/Cypress/4.1.0/Cypress/resources/app/packages/server/config/app.yml +# cat /github/home/.cache/Cypress/4.1.0/Cypress/resources/app/packages/server/config/app.yml diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 220dc1811c..82c0cc621a 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -14,6 +14,7 @@ const formWidgetsPage = require("../locators/FormWidgets.json"); const ApiEditor = require("../locators/ApiEditor.json"); const apiwidget = require("../locators/apiWidgetslocator.json"); const dynamicInputLocators = require("../locators/DynamicInput.json"); + let pageidcopy = " "; Cypress.Commands.add("CreateApp", appname => { @@ -1123,7 +1124,14 @@ Cypress.Commands.add("startServerAndRoutes", () => { cy.route("GET", "/api/v1/plugins").as("getPlugins"); cy.route("POST", "/api/v1/logout").as("postLogout"); - cy.route("GET", "/api/v1/configs/name/propertyPane").as("getPropertyPane"); + cy.route({ + method: "GET", + url: "**/api/v1/configs/name/propertyPane", + status: 200, + response: "fixture:../fixtures/propertyPaneResponse.json", + delay: 100, + }).as("getPropertyPane"); + cy.route("GET", "/api/v1/datasources").as("getDataSources"); cy.route("GET", "/api/v1/pages/application/*").as("getPagesForApp"); cy.route("GET", "/api/v1/pages/*").as("getPage"); diff --git a/app/client/cypress/support/index.js b/app/client/cypress/support/index.js index 624912201a..d234f015a8 100644 --- a/app/client/cypress/support/index.js +++ b/app/client/cypress/support/index.js @@ -13,8 +13,6 @@ // https://on.cypress.io/configuration // *********************************************************** require("cypress-xpath"); -const loginData = require("../fixtures/user.json"); -const inputData = require("../fixtures/inputdata.json"); let pageid; let appId; @@ -28,10 +26,10 @@ Cypress.on("uncaught:exception", (err, runnable) => { }); before(function() { - console.log("**** Got Cypress base URL as: ", process.env.CYPRESS_BASE_URL); cy.startServerAndRoutes(); - //cy.LogintoApp(loginData.username, loginData.password); - cy.LoginFromAPI(Cypress.env("username"), Cypress.env("password")); + const username = Cypress.env("USERNAME"); + const password = Cypress.env("PASSWORD"); + cy.LoginFromAPI(username, password); cy.visit("/applications"); cy.wait("@applications").should( "have.nested.property", @@ -45,14 +43,6 @@ before(function() { localStorage.setItem("AppName", appId); }); - /* - cy.generateUUID().then(uid => { - pageid = uid; - cy.Createpage(pageid); - cy.NavigateToWidgets(pageid); - localStorage.setItem("PageName", pageid); - }); -*/ cy.fixture("example").then(function(data) { this.data = data; }); @@ -64,9 +54,6 @@ beforeEach(function() { }); after(function() { - // ---commenting Publish app and Delete page as of now--- // - //cy.Deletepage(pageid); - //cy.PublishtheApp(); //-- Deleting the application by Api---// cy.DeleteAppByApi(); //-- LogOut Application---// diff --git a/app/client/cypress/test.sh b/app/client/cypress/test.sh index cbc96b79a9..4fe5b8a607 100755 --- a/app/client/cypress/test.sh +++ b/app/client/cypress/test.sh @@ -27,8 +27,8 @@ if [ "$target" == "ci" ]; then # On the CI server run the tests in parallel # This requires the projectId and the record_key to be configured in your environment variables. By default this is defined on the CI server echo "Got the Build ID: $BUILD_ID" - CYPRESS_PROJECT_ID=appsmith-project $(npm bin)/cypress run --headless --browser chrome \ - --record --key "random-key" --ci-build-id $BUILD_ID \ + $(npm bin)/cypress run --headless --browser chrome \ + --record --key "$CYPRESS_RECORD_KEY" --ci-build-id $BUILD_ID \ --parallel --group "Electrons on Gitlab CI" \ --spec "cypress/integration/Smoke_TestSuite/*/*" else diff --git a/app/client/docker/templates/nginx-linux.conf.template b/app/client/docker/templates/nginx-linux.conf.template index 9d1583fc62..924814d861 100644 --- a/app/client/docker/templates/nginx-linux.conf.template +++ b/app/client/docker/templates/nginx-linux.conf.template @@ -14,7 +14,6 @@ server { proxy_set_header X-Forwarded-Host $host; proxy_set_header Accept-Encoding ""; - sub_filter_once off; location / { proxy_pass http://localhost:3000; @@ -41,19 +40,19 @@ server { location /api { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_pass https://release-api.appsmith.com; + proxy_pass http://localhost:8080; } location /oauth2 { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_pass https://release-api.appsmith.com; + proxy_pass http://localhost:8080; } location /login { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_pass https://release-api.appsmith.com; + proxy_pass http://localhost:8080; } } @@ -103,19 +102,19 @@ server { location /api { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_pass https://release-api.appsmith.com; + proxy_pass http://localhost:8080; } location /oauth2 { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_pass https://release-api.appsmith.com; + proxy_pass http://localhost:8080; } location /login { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_pass https://release-api.appsmith.com; + proxy_pass http://localhost:8080; } }