Adding Github Action workflow to the client code base (#66)

This PR adds the build, test & package workflow to the client code base as well.

In order for us to run the Cypress tests, we also spin up a local server in a Docker container and run all our tests against that server. This ensures that our tests are faster to run as well.

We also introduce the concept of stubbing network requests by stubbing the API that fetches the property pane configuration from the server.

Results for the Cypress tests can be viewed at: https://dashboard.cypress.io/projects/eyxvp8/runs/
This commit is contained in:
Arpit Mohan 2020-07-16 11:20:46 +05:30 committed by GitHub
parent daeb39a8ce
commit a9ed054cbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1406 additions and 51 deletions

View File

@ -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/<branch_name>. 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

View File

@ -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

View File

@ -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

View File

@ -12,9 +12,5 @@
"json": false
},
"viewportHeight": 900,
"viewportWidth": 1400,
"env": {
"username": "",
"password": ""
}
"viewportWidth": 1400
}

View File

@ -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

View File

@ -1,4 +0,0 @@
{
"appname": "AutoDslCypress"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
{
"username": "testowner@appsmith.com",
"password": "own3rT3st1ng"
}

View File

@ -1,3 +1,5 @@
/// <reference types="Cypress" />
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)

View File

@ -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");

View File

@ -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;
},
});
};

View File

@ -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

View File

@ -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");

View File

@ -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---//

View File

@ -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

View File

@ -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;
}
}