Giter Club home page Giter Club logo

extensiveautomation-server's Introduction

ExtensiveAutomation

PyPI - Python Version

ExtensiveAutomation enable you to create custom workflows to automate your project.

  • a workflow is the combination of differents actions.
  • an action is individual python code source with enriched parameters.

The architecture is composed of 3 parts:

Table of contents

Server Installation

About the server

The server is the main part of the ExtensiveAutomation project.

It's is running on the following tcp ports:

  • tcp/8081: REST API
  • tcp/8082: Websocket tunnel for app client
  • tcp/8083: Websocket tunnel for agents

A user account is required, you can use the default ones or create your own account. Default user accounts and passwords:

  • admin/password
  • tester/password
  • monitor/password

YAML files storage can be split into different workspaces. The Common workspace is available by default, attached to the previous users.

PyPI package

  1. Run the following command

    python3 -m pip install extensiveautomation_server
  2. Type the following command on your shell to start the server

    extensiveautomation --start
  3. Finally, check if the server is running fine.

Docker image

  1. Downloading the image

    docker pull extensiveautomation/extensiveautomation-server:latest
  2. Start the container

    docker run -d -p 8081:8081 -p 8082:8082 -p 8083:8083 \
    --name=extensive extensiveautomation/extensiveautomation-server

    If you want to start the container with persistant tests data, go to Docker Hub page.

  3. Finally, check if the server is running fine.

Source code

  1. Clone this repository on your linux server

    git clone https://github.com/ExtensiveAutomation/extensiveautomation-server.git
    cd extensiveautomation-server/
  2. As precondition, install the additional python libraries with pip command:

    python3 -m pip install -r requirements.txt
  3. Start the server. On linux the server is running as daemon.

    cd src/
    python3 extensiveautomation.py --start
  4. Finally, check if the server is running fine.

Install plugins

You can add plugins to your extensive automation server to add more functionnalities like make http requests, send ssh commands. By default the server comes without plugins so you need to install them one by one according to your needs with pip commands.

Main plugins:

And many others (old ones)...

Server is running fine

Testing using Curl

Checking if the REST api working fine using curl or postman.

curl -s -X POST http://127.0.0.1:8081/session/login \
-H "Content-Type: application/json" \
-d '{"login": "admin", "password": "password"}' | jq .

success response:

{
    "cmd": "/session/login", 
    "message": "Logged in", 
    "session_id": "MjA1OWI1OTc1MWM0NDU2NDg4MjQxMjRjNWFmN2FkNThhO", 
    "expires": 86400, 
    "user_id": 1, 
    "levels": ["Administrator"], 
    "project_id": 1, 
    "api_login": "admin", 
    "api_secret": "6977aa6a443bd3a6033ebb52557cf90d24c79857", 
    "client-available": false, 
    "version": "",
    "name": ""
}

Understand the Data Storage

Get the location

All data necessary for the server is stored in a specific folder. The location of the storage can be found with the following command:

extensiveautomation --show-data-path
/<install_project>/ea/var/

Data storage overview:

var/
  tests/
    <project_id>/
        [...yaml files...]
  testsresult/
    <project_id>/
        <result_id>/
  logs/
    output.log
  data.db

Working with actions

Action is individual python code source with parameters and must be defined with YAML file.

About actions

You can create your own actions but some actions are available by default in the folder /actions Actions must be defined with YAML file.

The default ones:

Additional actions are available with plugins:

HelloWorld action

This following action is available in the data storage in "/actions/basic/" folder. This basic action shows how to write python source code with parameters in YAML format.

properties:
  parameters:
   - name: msg
     value: hello world
python: |
    class HelloWorld(Action):
        def definition(self):
            self.info(input("msg"))
    HelloWorld().execute()

Working with workflows

A workflow is the combination of differents actions and must be defined with YAML file. Parameters from actions can be easily overwritten and conditions between actions can be defined.

About workflows

You can create your own workflows but some workflows are available by default in the folder /workflows Workflows must be defined with YAML file.

The default ones:

Additional workflows are available with plugins:

HelloWorld workflow

This following workflow is available in the data storage in "/workflows/basic/" folder. This workflow shows how to use actions with updated parameters.

actions:
- description: HelloWorld
  file: Common:actions/basic/helloworld.yml
  parameters:
   - name: msg
     value: Hola Mundo

SSH workflow

This example describe how to write a ssh workflow to execute some commands remotely using SSH. The SSH plugin must be installed, please refer to the chapter Install plugins.

The following example show how to execute remote ssh commands.

actions:
- description: execute commands remotely using SSH 
  file: Common:actions/ssh/send_commands.yml
  parameters:
   - name: ssh-hosts
     value:
      - ssh-host: 10.0.0.55
        ssh-login: root
        ssh-password: ESI23xgx4yYukF9rsA1O
   - name: ssh-commands
     value: |-
        echo "hello world" >> /var/log/messages
        echo "hola mondu" >> /var/log/messages

If your want to run the previous workflow with a remote agent:

  • you must deploy a ssh agent
  • you must define the agent to use in your worflow like below
properties:
  parameters:
    - name: agent
      value: agent01.ssh

Additional examples are available in the data storage ./workflows/ssh/.

HTTP workflow

This example describe how to write a HTTP workflow to send HTTP requests. The WEB plugin must be installed, please refer to the chapter install plugins.

The following example show how to send a http request and waiting response with specific json key.

actions:
- description: Get my origin IP
  file: Common:actions/http/curl.yml
  parameters:
   - name: curl-hosts
     value: https://httpbin.org/ip
   - name: response-body-json
     value: |
        origin -> [!CAPTURE:externalip:]
- description: Log external IP
  file: Common:actions/cache/log.yml
  parameters:
   - name: key
     value: externalip

If your want to run the previous workflow with a remote agent:

  • you must deploy a curl agent
  • you must define the agent to use in your worflow like below
properties:
  parameters:
    - name: agent
      value: agent01.curl

Additional examples are available in the data storage ./workflows/http/.

Selenium workflow

This example describe how to write a Selenium workflow.

The GUI plugin must be installed, please refer to the chapter install plugins. and the selenium agent must installed and running.

The following example show how to open the web browser, that loads the url www.google.com and finally close-it after.

properties:
  parameters:
    - name: agent
      value: agent01.selenium
    - name: browser
      value: chrome
actions:
- description: open browser
  file: Common:actions/selenium/openbrowser.yml
  id: 1
  parameters:
   - name: url
     value: https://www.google.com
- description: close browser
  file: Common:actions/selenium/closebrowser.yml
  id: 2
  parent: 1

Additionals examples are available in the data storage ./workflows/selenium/.

Sikulix workflow

This example describe how to write a Sikulix workflow. Sikulix is nice to simulate keyboard and mouse interactions on your system.

The GUI plugin must be installed, please refer to the chapter install plugins. and the sikulix agent must installed and running.

The following example show how to execute Windows + R shorcut and run a command.

properties:
  parameters:
    - name: agent
      value: agent01.sikulix
actions:
- description: Type Windows+R on keyboard 
  file: Common:actions/sikulix/type_shorcut.yml
  id: 1
  parameters:
   - name: key
     value: KEY_WIN
   - name: other-key
     value: r
- description: "Type text: cmd"
  file: Common:actions/sikulix/type_text.yml
  id: 1
  parent: 1
  parameters:
   - name: text
     value: cmd

Automation using the Web Interface

About Web Client

The web client is optional because you can do everything from the REST API of the server. But on some cases, it's more user friendly to use the web interface to manage:

  • users
  • projects
  • variables
  • and more...

Schedule a job

Go to the menu Automation > Job > Add Job

Select the your action or worflow and click on the button CREATE

Get job logs

Go to the menu Automation > Executions and display Logs

Automation using the REST API

About API

You can do everything from the REST API, below a small overview. Take a look to the swagger.

Get api secret key

Get the API secret for the user admin

extensiveautomation --generate-api-key admin
API key: admin
API secret: 6977aa6a443bd3a6033ebb52557cf90d24c79857

Schedule a job

Make a POST on /v1/jobs with basic auth to create a job wich will execute your actions or workflows.

Copy/Paste the following curl command:

curl -s --user admin:6977aa6a443bd3a6033ebb52557cf90d24c79857 \
-d '{"yaml-file": "/workflows/basic/helloworld.yml"}' \
-H "Content-Type: application/json" \
-X POST http://127.0.0.1:8081/v1/jobs?workspace=1 | jq .

Success response:

{
    "cmd": "/v1/jobs",
    "message": "background", 
    "job-id": 2,
    "execution-id": "e57aaa43-325d-468d-8cac-f1dea822ef3a"
}

Get job logs

Make a GET on /v1/executions with basic auth to get logs generated by the job.

Copy/Paste the following curl command:

curl -s  --user admin:6977aa6a443bd3a6033ebb52557cf90d24c79857 \
"http://127.0.0.1:8081/v1/executions?workspace=1&id=eab41766-c9b6-4632-8a73-42232a431051" | jq .

Success response:

{
    "cmd": "/v1/executions",
    "execution-id": "e57aaa43-325d-468d-8cac-f1dea822ef3a", 
    "status": "complete", 
    "verdict": "pass", 
    "logs": "10:50:10.7241 task-started
    10:50:10.7243 script-started helloworld
    10:50:10.7309 script-stopped PASS 0.007
    10:50:10.7375 task-stopped 0.006909608840942383", 
    "logs-index": 156
}

More security

Adding reverse proxy

Adding a reverse proxy in the front of server enables to expose only one tcp port (8080) and to have a TLS link between the client and the server. Also, the default behaviour of the QT client and toolbox is to try to connect on the tcp/8080 port with ssl (can be modifed).

If you want to install a reverse proxy, please to follow this procedure.

  1. Install the example provided scripts\reverseproxy\extensiveautomation_api.conf in your apache instance. If you install the reverse proxy on a new server, don't forget to replace the 127.0.0.1 address by the ip of your extensive server.

    Listen 8080
    
    <VirtualHost *:8080>
      SSLEngine on
    
      SSLCertificateFile /etc/pki/tls/certs/localhost.crt
      SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
    
      LogLevel warn
      ErrorLog  /var/log/extensiveautomation_api_error_ssl_rp.log
      CustomLog /var/log/extensiveautomation_api_access_ssl_rp.log combined
    
      Redirect 307 / /rest/session/login
    
      ProxyPass /rest/ http://127.0.0.1:8081/
      ProxyPassReverse /rest/ http://127.0.0.1:8081/
      
      ProxyPass /wss/client/ ws://127.0.0.1:8082 disablereuse=on
      ProxyPassReverse /wss/client/ ws://127.0.0.1:8082 disablereuse=on
    
      ProxyPass /wss/agent/ ws://127.0.0.1:8083 disablereuse=on
      ProxyPassReverse /wss/agent/ ws://127.0.0.1:8083 disablereuse=on
    </VirtualHost>

    With this configuration in apache, the REST API is now running on the port tcp/8080 (tls).

  2. Checking if the REST api working fine with curl command.

    curl -X POST https://127.0.0.1:8080/rest/session/login --insecure \ 
    -H "Content-Type: application/json" \
    -d '{"login": "admin", "password": "password"}'

LDAP users authentication

By default, users are authenticated locally from database (by checking hash password). This behavior can be modified by using a remote authentication server. In this mode, you always need to add users in the local database.

Follow this procedure to enable LDAP authentication:

  1. Install python dependancies with the pip command:

    python3 -m pip install ldap3
  2. Configure the settings.ini file to enable ldap authentication and other stuff

    [Users_Session]
    
    ; enable ldap user authentication for rest api session only
    ; 0=disable 1=enable
    ldap-authbind=1
    ; remote addresses of your ldap servers
    ; ldaps://127.0.0.1:636 
    ldap-host=[ "ldap://127.0.0.1:389" ]
    ; username form
    ; uid=%%s,ou=People,dc=extensive,dc=local
    ldap-dn=[ "uid=%%s,ou=People,dc=extensive,dc=local" ]
  3. Restart the server

    cd src/
    python3 extensiveautomation.py --stop
    python3 extensiveautomation.py --start
  4. Check the new user authentication method

    curl -X POST http://127.0.0.1:8081/session/login \
    -H "Content-Type: application/json" \
    -d '{"login": "admin", "password": "password"}'

Migration from old version

Since version 22 of the server, a major change has been introduced on the test files. All the old tests in XML can still be used but they are obsolete. We must favor the new YAML format.

Tests convertion to YAML format

XML to YAML conversion can be done with the following command. A new YML file will be created automatically after converting the XML reading.

extensiveautomation --convert-to-yaml

About

This project is an effort, driven in my spare time.

Copyright Copyright (c) 2010-2023 Denis Machard [email protected]
License MIT
Docs https://extensiveautomation.readthedocs.io/en/latest/
Github https://github.com/ExtensiveAutomation
Docker Hub https://hub.docker.com/u/extensiveautomation
PyPI https://pypi.org/project/extensiveautomation-server/
Google Users https://groups.google.com/group/extensive-automation-users
Twitter https://twitter.com/Extensive_Auto

extensiveautomation-server's People

Contributors

dbr13 avatar dependabot[bot] avatar dmachard avatar eagle842 avatar jordanefillatre avatar

Stargazers

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

extensiveautomation-server's Issues

Can't start client 18.0.0 (crash during start)

description of the issue

When I launch the client 18.0.0, it crashs:

In windows event viewer, I have:
Faulting application name: ExtensiveTestingClient.exe, version: 0.0.0.0, time stamp: 0x5a2e9f14
Faulting module name: ntdll.dll, version: 6.1.7601.23915, time stamp: 0x59b94ee4
Exception code: 0xc0000005
Fault offset: 0x000000000004f23c
Faulting process id: 0x1a78
Faulting application start time: 0x01d3c5c564fa0361
Faulting application path: C:\Program Files\Extensive Testing Client\ExtensiveTestingClient.exe
Faulting module path: C:\Windows\SYSTEM32\ntdll.dll
Report Id: a7be05c1-31b8-11e8-b0b7-847beb26756d

steps to reproduce the issue

install client 18.0.0 (on a windows 7 professional), and launch the Client

additionnals informations like versions, logs, screenshots

As ask in the google group, I try with a version 19.0.0alpha version. I have the same crash. Logs are in the attached zip

Logs_ExtensiveTestingClient_19.0.0alpha1_64bit_Portable.zip

hping3 is not installed

Je suis tombé sur cette erreur en lançant le test utilisant l'adaptateur Pinger :

hping3

J'ai téléchargé hping3 ici : hping3

Et pour l'installer sur une rhel7 :

cd hping-master
yum install libpcap-devel-1.5.3-9.el7.x86_64
ln -s /usr/include/pcap/bpf.h /usr/include/net/bpf.h
./configure
make
make install

Correction VSphere.py

Hello,

Je n'ai pas encore regardé comment faire de pull request alors en attendant je fais une issue :

Il y a une petite erreur à cause d'une mauvais nom de variable. Dans le fichier TestInteropLib/VSphere.py,
fonction cloneVm, dans le content des logs, la variable vmName n'existe pas :

content = { "result": "success",  "cmd": "clone-vm", 'vm-name': vmName }
tpl = self.template(name=self.__class__.__name__.upper(), content=content )
self.logResponse(msg="clone vm", details=tpl )

Je crois qu'il faut remplacer vmName par toVm. Ce n'est pas bloquant mais dans les logs XTC on obtient toujours une erreur de clonage.

No SSH disconnect logRecvEvent

description of the issue

When I do a SSH disconnect, the logRecvEvent never arrive.

xtcdisco

steps to reproduce the issue

	def prepare(self):
		self.ADPSSH = SutAdapters.Generic.SSH.Terminal(parent=self, destIp="10.10.10.10", destPort=22, bindIp='0.0.0.0', bindPort=0, login='admin', password='admin', privateKey=None, privateKeyPath=None, verbose=True, name=None, debug=True, logEventSent=True, logEventReceived=True, parentName=None, shared=False, agent=None, agentSupport=False, terminalWidth=400, terminalHeight=400, cycleSnap=1)
		
		self.ADPSSH.connect()
		res = self.ADPSSH.isOpened(timeout=10.0, message=None)
		if res:
			self.warning("open")

		self.ADPSSH.disconnect()		
		res=self.ADPSSH.isClosed(timeout=60)
		if res:
			self.warning("close")

additionnals informations like versions, logs, screenshots

Adapters v1100
SSH Terminal

[Plugin QC ]Error exporting results to TestLab

When exporting results, a REST error with invalid testpath is displayed and the results are not exported.

The workaround:

  • click on the plugin HP ALM QC to export the qc
  • uncheck the "ignore testcase" button (the test will appear on the list)
  • re-check the "ignore testcase" button (the test is still in the list)
  • select the test and try to export.

Gives the error: "unable to find the test on TestPlan" (but the test is created on TestPlan)

problem with successive scheduled task

When I try to use the scheduler to run many times a task, only the 1st execution is done.
I've tried with 'successive' and 'every' (I try with and without a number of runs)

I have client 21.0.0, and server 21.4.0
My task is a TestPlan with 2 TestSuites

some logs:

2020-10-27 15:42:59,639 - INFO - User Registered: ConnID=2 Addr=('127.0.0.1', 61031) Login=admin
2020-10-27 15:44:39,012 - INFO - Task registered with id 6
2020-10-27 15:44:39,015 - INFO - Starting test 6
2020-10-27 15:45:06,089 - INFO - Task re-scheduled with id 7
2020-10-27 15:45:06,090 - INFO - Starting test 7
2020-10-27 15:45:06,101 - ERROR - Task > prepare: unable to create the main te: startswith first arg must be bytes or a tuple of bytes, not str
2020-10-27 15:45:06,237 - ERROR - Task > run: unable to prepare test
Exception in thread Thread-30:
Traceback (most recent call last):
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/serverengine/TaskManager.py", line 1462, in prepare
var_path=Settings.get('Paths', 'testsresults')
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/testcreatorlib/TestModel.py", line 104, in createTestExecutable
var_path)
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/testcreatorlib/TestModel.py", line 881, in createTestPlan
parameters=parameters, user=userName)
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/testcreatorlib/TestModelCommon.py", line 327, in loadDataset
if pr['value'].startswith('undefined:/'):
TypeError: startswith first arg must be bytes or a tuple of bytes, not str

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/serverengine/TaskManager.py", line 2024, in run
testready = self.prepare(envTmp=False)
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/serverengine/TaskManager.py", line 1467, in prepare
raise Exception(getBackTrace())
Exception: Traceback (most recent call last):
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/serverengine/TaskManager.py", line 1462, in prepare
var_path=Settings.get('Paths', 'testsresults')
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/testcreatorlib/TestModel.py", line 104, in createTestExecutable
var_path)
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/testcreatorlib/TestModel.py", line 881, in createTestPlan
parameters=parameters, user=userName)
File "/mnt/c/temp/linuxFolder/thingpark-integration-automation/extensiveautomation-server/src/ea/testcreatorlib/TestModelCommon.py", line 327, in loadDataset
if pr['value'].startswith('undefined:/'):
TypeError: startswith first arg must be bytes or a tuple of bytes, not str

Regression version 18

Hello,

Voici deux soucis que j'ai rencontré lors de l'installation de la version 18 :

  • Il manque une tabulation dans le bloc prepare (lors de l’utilisation des adapters sikulix et selenium)

xtcerrr

Du coup, on est obligé de rajouter la tabulation à la main à chaque modification via l’assistant.

Erreur dans le fichier TestExecutorLib.py

Il y a une erreur dans la fonction virtualRun du fichier TestExecutorLib.py.
J'ai remplacé la ligne return _executerun par return not _ExecuteTest :

def virtualRun(self):
    """
    todo
    """
    return not _ExecuteTest
    #return _executerun -> Erreur 

Telnet Adapter error: « unable to pack options : need more than 1 value to unpack »

Using the Telnet client:

self.telnetObj = SutAdapters.Telnet.Client(parent=self, name=input('ADAPTER_NAME'), shared=input('SHARED'), destIp=input('TELNET_HOST'),destPort=input('TELNET_PORT'),debug=input('DEBUG'),agentSupport=input('AGENT_SUPPORT'))

The same code works for other equipment's and via Windows cmd line no problems connecting via telnet to the equipment. The output from telnet appears to have some strange characters, for other equipment I can see the login prompt, for this one no.

Output:

error
,

Pip modules were not installed on version 19

The script pip.sh uses /usr/bin/pip as pip home. Maybe detecting with "whereis pip" command. But probably should work with every setup, ours use custom python and could be the cause. If we use the default Python the error should not occur.

Proposition d'amélioration

Hello,

  • Dans l'adapter SSL, dans client, fonction initSocketSsl, j'ai rajouté le paramètre ciphers lorsqu'on veut préciser quel cipher utiliser :

exxc

  • Dans le test initEnvironment on ne peut pas boucler sur une instances précise pour chaque serveur :

Prenons deux serveurs avec deux instances SSH : { ADMIN : ... }, { PROD : ... }
On peut boucler sur les deux instances SSH sur nos deux serveurs du coup le script s'exécutera sur ADMIN et PROD.
Cependant on ne peut pas préciser de boucler sur nos deux serveurs uniquement sur l'instance ADMIN ou PROD.

J'ai fait la modification sur notre script mais il ne ressemble pas tout à fait à la version dispo dans le initialize de la v18. Je peux essayer de retrouver le bout de code que j'ai rajouté mais ça fait longtemps. Et puis ça augmente un peu le temps d'initialisation.

Du coup j'avais pensé à rajouter un bool : if input('PREPARE_ENV'):
On set la variable PREPARE_ENV à false dans notre testplan et dans le test d'initialisation on crée un alias de PREPARE_ENV que l'on met à true pour qu'il soit initialiser qu'une fois. Ca fait gagner pas mal de temps lorsqu'il y a beaucoup de test. Par contre il ne faut pas reset le cache dans les tests suites ...

Error executing test plan with REST interface

Utilisant l’interface REST pour exécuter un test plan avec l’URL : http://.../rest/tests/schedule/tpg et avec test inputs conduit à une erreur : « "error": "Internal server error encountered." »

Le body du request :

{
"test-path": "/Tests/Accessibilite",
"test-name": "[AUT] Test Connection Juniper EX3300 Haut",
"test-extension": "tpx",
"project-id": 2,
"schedule-id": 0,
"test-inputs": [
{"name": "QC_TESTSET_PATH" , "type": "str", "value": "testSetPath"},
{"name": "QC_TESTSET_NAME" , "type": "str", "value": "CurrentTestSet"},
{"name": "QC_INSTANCE_NAME" , "type": "str", "value": "instanceName"},
{"name": "QC_WRITE_RESULT" , "type": "bool", "value": "False"}
]
}

Sans le champ « test-inputs » la exécution ça fonctionne. Avec le champ je vois dans le server log :

'scope'
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/pycnic/core.py", line 204, in iter
resp = self.delegate()
File "/usr/local/lib/python2.7/site-packages/pycnic/core.py", line 269, in delegate
output = func(*args)
File "/opt/xtc/ExtensiveAutomation-19.0.1/ServerControls/RestTesterFunctions.py", line 54, in _to_yaml
return wrapped(*args, **kwargs)
File "/opt/xtc/ExtensiveAutomation-19.0.1/ServerControls/RestTesterFunctions.py", line 6158, in post
origInp["scope"] = newInp["scope"]
KeyError: 'scope'

Unable to build test designs in version 19

description of the issue

In the client, when trying launch a design of a test after opening it, I get the error message :

Unable to prepare the design of the test!
Syntax error detected in this test!

The design after test execution works though.

steps to reproduce the issue

  1. Open a test unit/suite/plan
  2. Click on the "Design" button.

additionnals informations like versions, logs, screenshots

image

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.