Skip to content

Commit 26da185

Browse files
committed
Added files
1 parent 5a469d8 commit 26da185

8 files changed

+385
-0
lines changed

.DS_Store

6 KB
Binary file not shown.

.github/.DS_Store

6 KB
Binary file not shown.

.github/workflows/magicpod_test.yml

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
on: [push]
2+
jobs:
3+
magic_pod_job:
4+
runs-on: macos-latest
5+
steps:
6+
- name: Checkout
7+
uses: actions/checkout@v4
8+
- name: Set up Python
9+
uses: actions/setup-python@v4
10+
- name: Install python dependencies
11+
run: |
12+
python -m pip install --upgrade pip
13+
pip install requests
14+
- name: Prepare testplan
15+
env:
16+
TESTRAIL_URL: <YOUR URL>
17+
TESTRAIL_USER: <YOUR USER ACCOUNT>
18+
TESTRAIL_PASSWORD: ${{ secrets.TESTRAIL_PASSWORD }}
19+
run: python testrail_prepare.py
20+
- name: Install magicpod-api-client
21+
env:
22+
MAGICPOD_API_TOKEN: ${{ secrets.MAGICPOD_API_TOKEN }}
23+
run: bash download_magicpod_api_client.sh
24+
- name: Run MagicPod
25+
env:
26+
MAGICPOD_API_TOKEN: ${{ secrets.MAGICPOD_API_TOKEN }}
27+
MAGICPOD_ORGANIZATION_NAME: <YOUR ORGANIZATION_NAME>
28+
MAGICPOD_PROJECT_NAME: <YOUR PROJECT_NAME>
29+
MAGICPOD_TEST_SETTING_ID: <YOUR SETTING_ID>
30+
run: python run_magicpod.py
31+
- name: Add test results to TestRail
32+
env:
33+
TESTRAIL_URL: <YOUR URL>
34+
TESTRAIL_USER: <YOUR USER NAME>
35+
TESTRAIL_PASSWORD: ${{ secrets.TESTRAIL_PASSWORD }}
36+
run: python testrail_add_test_result.py

download_magicpod_api_client.sh

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
OS=mac
2+
FILENAME=magicpod-api-client
3+
curl -L "https://app.magicpod.com/api/v1.0/magicpod-clients/api/${OS}/latest/" -H "Authorization: Token ${MAGICPOD_API_TOKEN}" --output ${FILENAME}.zip
4+
unzip -q ${FILENAME}.zip

run_magicpod.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import os
2+
import re
3+
import requests
4+
import sys
5+
import json
6+
import inspect
7+
import subprocess
8+
import zipfile
9+
import shutil
10+
11+
class MagicpodApiClientWrapper:
12+
def __init__(self, secret_api_token, org_name, project_name, cmd_path, tmp_dir):
13+
self._secret_api_token = secret_api_token
14+
self._org_name = org_name
15+
self._project_name = project_name
16+
self._cmd_path = cmd_path
17+
self._tmp_dir = tmp_dir
18+
19+
def _run_command(self, command):
20+
try:
21+
result = subprocess.run(
22+
command,
23+
stdout=subprocess.PIPE,
24+
stderr=subprocess.PIPE,
25+
text=True, # Get the output as text mode
26+
check=True # Raise an error is error code is not 0
27+
)
28+
return result.stdout, result.stderr
29+
except subprocess.CalledProcessError as e:
30+
return None, f"Error: error code {e.returncode} "
31+
except FileNotFoundError:
32+
return None, "Error: Command not found"
33+
34+
def batch_run(self, setting_id):
35+
command = [
36+
self._cmd_path,
37+
"batch-run",
38+
"-t", self._secret_api_token,
39+
"-o", self._org_name,
40+
"-p", self._project_name,
41+
"-S", str(setting_id)
42+
]
43+
stdout, stderr = self._run_command(command=command)
44+
print(command)
45+
print(stdout)
46+
print(stderr)
47+
return stdout
48+
49+
def get_latest_batch_number(self):
50+
latest_number = 0
51+
url = f"https://magic-pod.com/api/v1.0/{self._org_name}/{self._project_name}/batch-runs/?count=1"
52+
headers = {
53+
"Authorization": f"Token {self._secret_api_token}"
54+
}
55+
response = requests.get(url, headers=headers)
56+
if response.status_code == 200:
57+
result = response.json()
58+
latest_number = result['batch_runs'][0]['batch_run_number']
59+
return latest_number
60+
61+
def get_batch_run(self, batch_run_number):
62+
url = f"https://magic-pod.com/api/v1.0/{self._org_name}/{self._project_name}/batch-run/{batch_run_number}"
63+
headers = {
64+
"Authorization": f"Token {self._secret_api_token}"
65+
}
66+
response = requests.get(url, headers=headers)
67+
if response.status_code == 200:
68+
result = response.json()
69+
return result
70+
71+
def run_magicpod(output_filename, magicpod_api_client_path, temp_dir):
72+
if not temp_dir.endswith('/'):
73+
temp_dir += '/'
74+
MAGICPOD_API_TOKEN = os.environ.get("MAGICPOD_API_TOKEN")
75+
MAGICPOD_ORGANIZATION_NAME = os.environ.get("MAGICPOD_ORGANIZATION_NAME")
76+
MAGICPOD_PROJECT_NAME = os.environ.get("MAGICPOD_PROJECT_NAME")
77+
MAGICPOD_TEST_SETTING_ID = os.environ.get("MAGICPOD_TEST_SETTING_ID")
78+
client = MagicpodApiClientWrapper(secret_api_token=MAGICPOD_API_TOKEN, org_name=MAGICPOD_ORGANIZATION_NAME, project_name=MAGICPOD_PROJECT_NAME, cmd_path=magicpod_api_client_path, tmp_dir=temp_dir)
79+
# Run MagicPod tests
80+
client.batch_run(MAGICPOD_TEST_SETTING_ID)
81+
# Get test results
82+
latest_batch_number = client.get_latest_batch_number()
83+
test_results = client.get_batch_run(latest_batch_number)
84+
# Save test results in a file
85+
with open(output_filename, "w", encoding='utf-8') as file:
86+
file.write(json.dumps(test_results))
87+
88+
run_magicpod(output_filename='./magicpod_result',magicpod_api_client_path='./magicpod-api-client', temp_dir='./')

testrail.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""TestRail API binding for Python 3.x.
2+
3+
(API v2, available since TestRail 3.0)
4+
5+
Compatible with TestRail 3.0 and later.
6+
7+
Learn more:
8+
9+
http://docs.gurock.com/testrail-api2/start
10+
http://docs.gurock.com/testrail-api2/accessing
11+
12+
Copyright Gurock Software GmbH. See license.md for details.
13+
"""
14+
15+
import base64
16+
import json
17+
18+
import requests
19+
20+
21+
22+
class APIClient:
23+
def __init__(self, base_url):
24+
self.user = ''
25+
self.password = ''
26+
if not base_url.endswith('/'):
27+
base_url += '/'
28+
self.__url = base_url + 'index.php?/api/v2/'
29+
30+
def send_get(self, uri, filepath=None):
31+
"""Issue a GET request (read) against the API.
32+
33+
Args:
34+
uri: The API method to call including parameters, e.g. get_case/1.
35+
filepath: The path and file name for attachment download; used only
36+
for 'get_attachment/:attachment_id'.
37+
38+
Returns:
39+
A dict containing the result of the request.
40+
"""
41+
return self.__send_request('GET', uri, filepath)
42+
43+
def send_post(self, uri, data):
44+
"""Issue a POST request (write) against the API.
45+
46+
Args:
47+
uri: The API method to call, including parameters, e.g. add_case/1.
48+
data: The data to submit as part of the request as a dict; strings
49+
must be UTF-8 encoded. If adding an attachment, must be the
50+
path to the file.
51+
52+
Returns:
53+
A dict containing the result of the request.
54+
"""
55+
return self.__send_request('POST', uri, data)
56+
57+
def __send_request(self, method, uri, data):
58+
url = self.__url + uri
59+
60+
auth = str(
61+
base64.b64encode(
62+
bytes('%s:%s' % (self.user, self.password), 'utf-8')
63+
),
64+
'ascii'
65+
).strip()
66+
headers = {'Authorization': 'Basic ' + auth}
67+
68+
if method == 'POST':
69+
if uri[:14] == 'add_attachment': # add_attachment API method
70+
files = {'attachment': (open(data, 'rb'))}
71+
response = requests.post(url, headers=headers, files=files)
72+
files['attachment'].close()
73+
else:
74+
headers['Content-Type'] = 'application/json'
75+
payload = bytes(json.dumps(data), 'utf-8')
76+
response = requests.post(url, headers=headers, data=payload)
77+
else:
78+
headers['Content-Type'] = 'application/json'
79+
response = requests.get(url, headers=headers)
80+
81+
if response.status_code > 201:
82+
try:
83+
error = response.json()
84+
except: # response.content not formatted as JSON
85+
error = str(response.content)
86+
raise APIError('TestRail API returned HTTP %s (%s)' % (response.status_code, error))
87+
else:
88+
if uri[:15] == 'get_attachment/': # Expecting file, not JSON
89+
try:
90+
open(data, 'wb').write(response.content)
91+
return (data)
92+
except:
93+
return ("Error saving attachment.")
94+
else:
95+
try:
96+
return response.json()
97+
except: # Nothing to return
98+
return {}
99+
100+
101+
102+
class APIError(Exception):
103+
pass

testrail_add_test_result.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import os
2+
import re
3+
import sys
4+
import base64
5+
import json
6+
import requests
7+
import argparse
8+
from datetime import datetime
9+
from testrail import *
10+
11+
class TestRailAPIWrapper:
12+
13+
def __init__(self, base_url, user, password):
14+
self._client = APIClient(base_url)
15+
self._client.user = user
16+
self._client.password = password
17+
18+
def get_tests(self, run_id):
19+
# https://docs.testrail.techmatrix.jp/testrail/docs/702/api/reference/tests/
20+
# GET index.php?/api/v2/get_tests/:run_id
21+
response = self._client.send_get(
22+
'get_tests/'+str(run_id))
23+
return response['tests']
24+
25+
def add_result(self, test_id, entries):
26+
# https://docs.testrail.techmatrix.jp/testrail/docs/702/api/reference/results/
27+
response = self._client.send_post(
28+
'add_result/'+str(test_id),entries)
29+
return response
30+
31+
TESTRAIL_URL = os.environ.get("TESTRAIL_URL")
32+
TESTRAIL_USER = os.environ.get("TESTRAIL_USER")
33+
TESTRAIL_PASSWORD = os.environ.get("TESTRAIL_PASSWORD")
34+
TESTRAIL_TESTPLAN_JSON_FILENAME = './testplan.json'
35+
36+
def add_result(json_filename):
37+
print(f"Adding the result according to the JSON file: {json_filename}")
38+
39+
if not os.path.exists(json_filename):
40+
print("Error: json file is not found.")
41+
sys.exit(1)
42+
43+
if not os.path.exists(TESTRAIL_TESTPLAN_JSON_FILENAME):
44+
print("Error: testplan file is not found.")
45+
sys.exit(1)
46+
47+
with open(TESTRAIL_TESTPLAN_JSON_FILENAME, "r") as f:
48+
testplan_data = json.load(f)
49+
print(json.dumps(testplan_data, indent=4))
50+
51+
with open(json_filename, "r") as f:
52+
magicpod_result_data = json.load(f)
53+
print(json.dumps(magicpod_result_data, indent=4))
54+
55+
client = TestRailAPIWrapper(TESTRAIL_URL, TESTRAIL_USER, TESTRAIL_PASSWORD)
56+
57+
magicpod_patterns = magicpod_result_data['test_cases']['details']
58+
testrail_testruns = testplan_data['entries'][0]['runs']
59+
for magicpod_pattern in magicpod_patterns:
60+
for testrail_testrun in testrail_testruns:
61+
if testrail_testrun['config'] == magicpod_pattern['pattern_name']: # browser name
62+
testrail_tests = client.get_tests(testrail_testrun['id'])
63+
for testrail_test in testrail_tests:
64+
pattern = r"\/(\d+)\/$"
65+
test_case_id_registered_in_testrail = re.search(pattern, testrail_test['custom_magicpod_url']).group(1)
66+
magicpod_test_results = magicpod_pattern['results']
67+
for magicpod_test_result in magicpod_test_results:
68+
if int(test_case_id_registered_in_testrail) == int(magicpod_test_result['test_case']['number']):
69+
if magicpod_test_result['status'] == "succeeded":
70+
status = 1
71+
elif magicpod_test_result['status'] == "failed":
72+
status = 5
73+
74+
started_at = datetime.fromisoformat(magicpod_test_result['started_at'])
75+
if magicpod_test_result['finished_at'] == "":
76+
finished_at = datetime.fromisoformat(datetime.now().strftime("%Y-%m-%dT%H:%M:%S"))
77+
else:
78+
finished_at = datetime.fromisoformat(magicpod_test_result['finished_at'])
79+
elapsed_seconds = str((finished_at - started_at).total_seconds()) + "s"
80+
81+
comment = f"MagicPod URL:{magicpod_result_data['url']}"
82+
83+
result_data = {
84+
"status_id": status,
85+
"comment": comment,
86+
"elapsed": elapsed_seconds,
87+
}
88+
89+
# 登録
90+
add_result_response = client.add_result(testrail_test['id'], result_data)
91+
print(json.dumps(add_result_response, indent=4))
92+
93+
add_result('./magicpod_result')

testrail_prepare.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
import sys
3+
import base64
4+
import json
5+
import requests
6+
import argparse
7+
from datetime import datetime
8+
from testrail import *
9+
10+
class TestRailAPIWrapper:
11+
12+
def __init__(self, base_url, user, password):
13+
self._client = APIClient(base_url)
14+
self._client.user = user
15+
self._client.password = password
16+
17+
def add_plan(self, project_id, entries):
18+
# https://docs.testrail.techmatrix.jp/testrail/docs/702/api/reference/plans/
19+
# POST index.php?/api/v2/add_plan/:project_id
20+
entries["name"] = datetime.now().strftime("%Y-%m-%d-%H-%M") + " MagicPod Test"
21+
response = self._client.send_post(
22+
'add_plan/'+str(project_id),entries)
23+
return response
24+
25+
TESTRAIL_URL = os.environ.get("TESTRAIL_URL")
26+
TESTRAIL_USER = os.environ.get("TESTRAIL_USER")
27+
TESTRAIL_PASSWORD = os.environ.get("TESTRAIL_PASSWORD")
28+
TESTRAIL_TESTPLAN_JSON_FILENAME = './testplan.json'
29+
TESTRAIL_PROJECT_ID = 3 # [ToDo]Change to the target project id in TestRail
30+
TESTRAIL_TESTPLAN_ENTRY = {
31+
"name": "testplan_name",
32+
"entries": [
33+
{
34+
"suite_id": 3, # [ToDo]Change to the target suite_id in TestRail
35+
"include_all": True, # All test cases in a suite will be included in the test plan
36+
"config_ids": [5,6,7], # [ToDo]Change to the target config ids
37+
"runs": [
38+
{
39+
"config_ids": [5] # [ToDo]Change to the target config id
40+
},
41+
{
42+
"config_ids": [6] # [ToDo]Change to the target config id
43+
},
44+
{
45+
"config_ids": [7] # [ToDo]Change to the target config id
46+
}
47+
]
48+
},
49+
]
50+
}
51+
52+
53+
def prepare_testplan():
54+
print("Preparing test plan")
55+
client = TestRailAPIWrapper(TESTRAIL_URL, TESTRAIL_USER, TESTRAIL_PASSWORD)
56+
response = client.add_plan(TESTRAIL_PROJECT_ID, TESTRAIL_TESTPLAN_ENTRY)
57+
print(json.dumps(response, indent=4))
58+
with open(TESTRAIL_TESTPLAN_JSON_FILENAME, "w", encoding='utf-8') as file:
59+
file.write(json.dumps(response))
60+
61+
prepare_testplan()

0 commit comments

Comments
 (0)