Code Monkey home page Code Monkey logo

replicant's Introduction

Replicant

Go Report Card GoDoc Docker Cloud Automated build

Replicant is a synthetic testing service named after the bioengineered androids from Blade Runner. (all synthetics came from Blade Runner :)

It allows web application testing using chromedp, and api application testing using Go or Javascript. Provides a test manager, execution scheduler, api and facilities for emitting result data to external systems.

Status

Under heavy development and API changes are expected. Please file an issue if anything breaks.

Requirements

  • Go > 1.15

Runing replicant

The replicant binary packs all functionality needed to run the server, executor and run local execution of tests for development or CI/CD purposes.

Locally for test development purposes

/path/to/replicant run --file api-test.yaml

If running locally from with the replicant binary a local chrome web browser with the development protocol can be specified:

/path/to/replicant run --chrome-remote-url http://127.0.0.1:9222  --file web-test.yaml

To have the local chrome browser started with the developer protocol enabled:

/path/to/chrome --remote-debugging-port=9222

Configuration options

Please see:

/path/to/replicant --help

Replicant server and executor locally with docker

The unbabel/replicant docker image packs everything needed to run and manage tests for both web apps and APIs. See the example docker-compose.yaml for more information.

docker stack deploy -c $PWD/docker-compose.yaml replicant

This will deploy the replicant server and 2 replicant executor nodes for web tests.

Web application testing

Web application testing support is based on the FQL (Ferret Query Language), documentation.

Test definition (can be also in json format)

POST http://127.0.0.1:8080/api/v1/run
content-type: application/yaml

name: duckduckgo-web-search
driver: web
schedule: '@every 60s'
timeout: 50s
retry_count: 2
inputs:
  url: "https://duckduckgo.com"
  user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36"
  timeout: 5000000
  text: "blade runner"
metadata:
  transaction: website-search
  application: duckduckgo
  environment: production
  component: website
script: |
  LET doc = DOCUMENT('{{ index . "url" }}', { driver: "cdp", userAgent: "{{ index . "user_agent" }}"})
  INPUT(doc, '#search_form_input_homepage', "{{ index . "text" }}")
  CLICK(doc, '#search_button_homepage')
  WAIT_NAVIGATION(doc)
  LET result = ELEMENT(doc, '#r1-0 > div > div.result__snippet.js-result-snippet').innerText
  RETURN {
    failed: result == "",
    message: "search result",
    data: result,
  }

Response

{
  "data": [
    {
      "uuid": "01DSSR5GH2BPX4G5FFCEVPEBKK",
      "name": "duckduckgo-web-search",
      "driver": "web",
      "failed": true,
      "message": "",
      "data": "",
      "time": "2019-11-16T09:19:39.554976Z",
      "metadata": {
        "application": "duckduckgo",
        "component": "website",
        "environment": "production",
        "transaction": "website-search"
      },
      "retry_count": 0,
      "with_callback": false,
      "duration_seconds": 6.967938203,
      "error": "operation timed out: WAIT_NAVIGATION(doc) at 4:0"
    }
  ]
}

API testing

Using the javascript driver

The following API is exposed by the javascript driver in order to perform HTTP calls and logging:

  • replicant.Log(string) log messages from the javascript test on the replicant server log.

  • replicant.NewResult() create a new response object to be returned as a result of the test, which should be modified accordingly to reflect the test result. The response must be returned as a serialized JSON object by calling its bounded method Response.JSON, E.g. return response.JSON().

Result type attributes:

{
		Data: "",
		Message: "",
		Failed: false,
}
  • replicant.http.NewRequest() creates a new HTTP request object for performing HTTP calls.

HttpRequest attributes:

{
		URL: "",
		Method: "",
		Body: "",
		Header: {},
		Params: {},
		FormData: {},
		SSLSkipVerify: false,
  • `replicant.http.Do(HttpRequest) performs a HTTP request and returns its response.

HttpResponse attributes:

{
	Status: ""
	StatusCode: 200
	Protocol: ""
	Body: ""
	Header: {}
	Error: ""
}

Test definition (can be also in JSON format)

POST http://127.0.0.1:8080/api/v1/run
content-type: application/yaml

name: duckduckgo-api-search
driver: javascript
schedule: '@every 60s'
timeout: 60s
retry_count: 2
inputs:
  url: "https://api.duckduckgo.com"
  text: "blade runner"
metadata:
  transaction: api-search
  application: duckduckgo
  environment: production
  component: api
script: |
  function Run(ctx) {
    req = replicant.http.NewRequest()
    req.URL = "{{ index . "url" }}"
    req.Params.q = "{{ index . "text" }}"
    req.Params.format = "json"
    req.Params.no_redirect = "1"
    resp = replicant.http.Do(req)
    data = JSON.parse(resp.Body)
    rr = replicant.NewResponse()
    switch(data.RelatedTopics && data.RelatedTopics.length > 0) {
      case true:
        rr.Data = data.RelatedTopics[0].Text
        rr.Message = resp.Status
        rr.Failed = false
        break
      case false:
        rr.Data = JSON.stringify(data)
        rr.Message = resp.Status
        rr.Failed = true
        break
    }
    return rr.JSON()
  }
Using the Go driver

Standard Go code can be used to create tests using following rules:

  • The package name must be transaction
  • The test function must implement the following signature: func Run(ctx context.Context) (message string, data string, err error).

Keep in mind that unlike the javascript driver which doesn't expose any I/O or lower level functionality for accessing the underlying OS, the Go driver currently exposes all of the Go standard library. Only use this driver if you are absolutely sure of what you are doing. This is planned to change in the future.

Test definition (can be also in JSON format)

POST http://127.0.0.1:8080/api/v1/run
content-type: application/yaml

name: duckduckgo-api-search
driver: go
schedule: '@every 60s'
timeout: 60s
retry_count: 2
inputs:
  url: "https://api.duckduckgo.com/"
  text: "blade runner"
metadata:
  transaction: api-search
  application: duckduckgo
  environment: production
  component: api
script: |
  package transaction
  import "bytes"
  import "context"
  import "fmt"
  import "net/http"
  import "io/ioutil"
  import "net/http"
  import "regexp"
  func Run(ctx context.Context) (m string, d string, err error) {
    req, err := http.NewRequest(http.MethodGet, "{{ index . "url" }}", nil)
      if err != nil {
        return "request build failed", "", err
    }
    req.Header.Add("Accept-Charset","utf-8")
    q := req.URL.Query()
    q.Add("q", "{{ index . "text" }}")
    q.Add("format", "json")
    q.Add("pretty", "1")
    q.Add("no_redirect", "1")
    req.URL.RawQuery = q.Encode()
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
      return "failed to send request", "", err
    }
    buf, err := ioutil.ReadAll(resp.Body)
    if err != nil {
      return "failed to read response", "", err
    }
    rx, err := regexp.Compile(`"Text"\s*:\s*"(.*?)"`)
    if err != nil {
      return "failed to compile regexp", "", err
    }
    s := rx.FindSubmatch(buf)
    if len(s) < 2 {
      return "failed to find data", "", fmt.Errorf("no match")
    }
    return "search result", fmt.Sprintf("%s", s[1]), nil
  }

Response

{
  "data": [
    {
      "uuid": "01DSSR7ST5Q1Y2Y7HDSQDNS7Y7",
      "name": "duckduckgo-api-search",
      "driver": "go",
      "failed": false,
      "message": "search result",
      "data": "Blade Runner A 1982 American neo-noir science fiction film directed by Ridley Scott, written by Hampton...",
      "time": "2019-11-16T09:20:54.597852Z",
      "metadata": {
        "application": "duckduckgo",
        "component": "api",
        "environment": "production",
        "transaction": "api-search"
      },
      "retry_count": 0,
      "with_callback": false,
      "duration_seconds": 0.486582328,
      "error": ""
    }
  ]
}

API

Method Resource Action
POST /v1/transaction Add a managed transaction
GET /v1/transaction Get all managed transaction definitions
GET /v1/transaction/:name Get a managed transaction definition by name
DELETE /v1/transaction/:name Remove a managed transaction
POST /v1/run Run an ad-hoc transaction
POST /v1/run/:name Run a managed transaction by name
GET /v1/result Get all managed transaction last execution results
GET /v1/result/:name Get the latest result for a managed transaction by name
GET /metrics Get metrics (prometheus emitter must be enabled)
GET /debug/pprof Get available runtime profile data (debug enabled)
GET /debug/pprof/:profile Get profile data (for pprof, debug enabled)

TODO

  • Tests
  • Developer and user documentation
  • Add support for more conventional persistent stores
  • Vault integration for secrets (inputs)
  • Architecture and API documentation
  • Javascript driver transaction support

Acknowledgements

Contact

Bruno Moura [email protected]

License

Replicant source code is available under the Apache Version 2.0 License

replicant's People

Contributors

brunotm avatar gisson avatar nmpacheco avatar randytkk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

replicant's Issues

Bad flag chrome-enable-local

Describe the bug
By running the Replicant executor it is impossible to run chrome as a remote device.

To Reproduce
./replicant executor

Expected behavior
A normal launch of the executor connecting to the remote chromium, with a message similar to the following:
{"level":"info","time":"2020-02-27T23:01:30.345Z","caller":"cmd/executor.go:120","message":"starting replicant-executor"}

Desktop (please complete the following information):

  • OS: Gentoo/Linux
  • Browser [e.g. chrome, safari]
  • Version 0.2.0

Deployment

Hi, I am sorry I am new with Docker Compose and Go.
Can someone please suggest to me how to run this application and deploy it locally and how to check the features ?
Here's the steps I followed:

  1. Clone the repository
  2. sudo docker-compose up --build

This is giving the following output:

Creating network "replicant_default" with the default driver
Creating volume "replicant_data" with default driver
Pulling server (docker.io/unbabel/replicant:latest)...
latest: Pulling from unbabel/replicant
50c5b17671b8: Pull complete
91f56774ed74: Pull complete
7823a436863e: Pull complete
aacb5b5a2a91: Pull complete
68df9506142d: Pull complete
48dbd5d428ae: Pull complete
Digest: sha256:65ea53d99e54288f5b1a8a3f3b982d565871161677ae0f26648f3f8874a883a0
Status: Downloaded newer image for unbabel/replicant:latest
Creating replicant_executor_1 ... done
Creating replicant_executor_2 ... done
Creating replicant_server_1   ... done
Attaching to replicant_executor_1, replicant_executor_2, replicant_server_1
server_1    | {"level":"info","time":"2021-02-04T13:04:05.432Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/transaction","method":"POST"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.433Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/transaction","method":"GET"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.433Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/transaction/:name","method":"GET"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.433Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/transaction/:name","method":"DELETE"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.433Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/run","method":"POST"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.433Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/run/:name","method":"POST"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.433Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/result","method":"GET"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.433Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/result/:name","method":"GET"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.433Z","caller":"server/server.go:112","message":"adding handler","path":"/api/v1/callback/:uuid","method":"POST"}
server_1    | {"level":"info","time":"2021-02-04T13:04:05.434Z","caller":"cmd/server.go:117","message":"replicant server started","address":"0.0.0.0:8080"}
executor_1  | {"level":"info","time":"2021-02-04T13:04:06.346Z","caller":"web/driver.go:126","message":"chrome process created","pid":13}
executor_1  | Starting executor

I am going to localhost:8080 and it's giving me 404 error.
Can someone tell me if I am doing something wrong ??

Thanks

Decouple the replicant server API from test execution

Currently replicant has both the server component, API and test execution within the same component.

This brings several limitations where the most problematic case is the handling of remote chrome processes and limitations to scale horizontally the test infrastructure.

The intention is to decouple the storage, management, secrets, parsing, scheduling, data emitting, server api and callback handling into the replicant-server and the test execution into the replicant-executor (which will bundle everything needed to run tests).

Make replicant possible to use in other browsers

Is your feature request related to a problem? Please describe.
Not every user uses or wants to use Chrome. Plus as we have seen Chrome has problems with memory leakage and having other browsers would be a great way to "mitigate" the problem.

Describe the solution you'd like
I'd prefer if we could implement support for other protocols, following MontFerret/ferret#200

Describe alternatives you've considered

  • Implement the support for the browsers inside replicant with built-in drivers.
  • Make a program which will serve as translation layer between protocols.

Hashicorp Vault integration for transaction inputs

Is your feature request related to a problem? Please describe.

Transaction inputs can only be defined at the inputs field, which makes it hard to have version controlled tests without having to strip sensitive information and even harder to have test credentials to be rotated.

Describe the solution you'd like

Inputs should have a special field like "vault://secret/path" that would specify a secret path from a Vault system and populate the inputs with the key/value pairs available within the secret path.

This would must happen at before parsing/decoding at each transaction execution so existing scheduled tests will always have the latest data available from vault.

Additional context

The implementation should allow for pluggable data sources, eg: etcd://data/path.

Fail to build with go 1.14

Describe the bug
The project fails to build with go 1.14, outputting the following error:

driver/go/driver.go:28:2: build constraints exclude all Go files in /src/replicant/vendor/github.com/containous/yaegi/stdlib
make: *** [Makefile:8: build] Error 1

To Reproduce
The easiest and most efficient way is to change the Dockerfile version from 1.13 to 1.14. Afterwards run docker build .

Expected behavior
Normal build operation

Desktop (please complete the following information):

  • OS: Gentoo
  • GO version: go1.14 linux/amd64
  • GO env:
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/jorge/.cache/go-build"
GOENV="/home/jorge/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/jorge/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GCCGO="/usr/bin/gccgo"
AR="ar"
CC="x86_64-pc-linux-gnu-gcc"
CXX="x86_64-pc-linux-gnu-g++"
CGO_ENABLED="1"
GOMOD="/home/jorge/github/replicant/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build160057016=/tmp/go-build -gno-record-gcc-switches"

Unable to import Google's protobuf libs

Describe the bug
I'm unable to import the protobuf libraries developed by Google which is a basic requirement to develop test that have the the protobuf generated code inside.

To Reproduce
Steps to reproduce the behavior:

  1. Run Replicant v0.2.1
  2. Copy the Go example presented in the Readme
  3. Add the import for the proto libs
  4. Invoke the /api/v1/run
  5. Observe the "error": "driver/go: error initializing transaction script: 9:3: import \"github.com/golang/protobuf/proto\" error: unable to find source related to: \"github.com/golang/protobuf/proto\""

Expected behavior
i expected Replicant to detect and fetch the dependencies so that it could run such tests.

Screenshots
The response
image

The request payload

name: duckduckgo-api-search
driver: go
schedule: '@every 60s'
timeout: 60s
retry_count: 2
inputs:
  url: "https://api.duckduckgo.com"
  text: "blade runner"
metadata:
  transaction: api-search
  application: duckduckgo
  environment: production
  component: api
script: |
  package transaction

  // Code generated by protoc-gen-go. DO NOT EDIT.
  // source: translation.proto

  import (
    context "context"
    fmt "fmt"
    proto "github.com/golang/protobuf/proto"
    grpc "google.golang.org/grpc"
    codes "google.golang.org/grpc/codes"
    status "google.golang.org/grpc/status"
    math "math"
    "bytes"
    "context"
    "fmt"
    "net/http"
    "io/ioutil"
    "net/http"
    "regexp"
  )

  //The protobuf generated code is omitted due to business reasons

  func Run(ctx context.Context) (m string, d string, err error) {
    req, err := http.NewRequest(http.MethodGet, "{{ index . "url" }}", nil)
      if err != nil {
        return "request build failed", "", err
    }
    req.Header.Add("Accept-Charset","utf-8")
    q := req.URL.Query()
    q.Add("q", "{{ index . "text" }}")
    q.Add("format", "json")
    q.Add("pretty", "1")
    q.Add("no_redirect", "1")
    req.URL.RawQuery = q.Encode()
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
      return "failed to send request", "", err
    }
    buf, err := ioutil.ReadAll(resp.Body)
    if err != nil {
      return "failed to read response", "", err
    }
    rx, err := regexp.Compile(`"Text"\s*:\s*"(.*?)"`)
    if err != nil {
      return "failed to compile regexp", "", err
    }
    s := rx.FindSubmatch(buf)
    if len(s) < 2 {
      return "failed to find data", "", fmt.Errorf("no match")
    }
    return "search result", fmt.Sprintf("%s", s[1]), nil
  }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.