Monitor multiple remote log files with MultiTail

With MultiTail you are able to view one or multiple log files (on remote engines). Therefore it creates multiple split-windows on your console. You can configure these! To see all features look here.


# install on Mac OS via Mac Ports
$ sudo port install multitail

# install on Mac OS via Homebrew
$ sudo brew install multitail

# install on RHEL/CentOS/Fedora
$ yum install -y multitail

# install on Debian/Ubuntu/Mint
$ sudo apt-get install multitail


# example for two log-files
$ multitail log-file_a log-file_b

# example for two log-files and two columns
$ multitail -s 2 log-file_a log-file_a

# example for two log-files and different colors
$ multitail -ci green log-file_a -ci yellow -I log-file_a

# example for one log file on remote
$ multitail -l "ssh -t <user>@<host> tail -f log-file"

# example for four log files on remote
$ multitail -l "ssh -l <user> <host> tail -f log-file_a" -l "ssh -l <user> <host> tail -f log-file_b" -l "ssh -l <user> <host> tail -f log-file_c" -l "ssh -l <user> <host> tail -f log-file_d"


If you look on multiple files at same time with MultiTail – just hit the “b” key to select the window, with up/down keys you can scroll.

Create PDF test-reports with Python

With only two python libraries and some line of codes, you are able to create very nice test-reports in PDF format. This tutorial should give you a hint. Feel free to improve with your requirements.



# -*- coding: utf-8 -*-

import time
import matplotlib.pyplot as plt

from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_JUSTIFY
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch

class CreateTestReport(object):
    """Example test report class"""

    def __init__(self, test_results, file_name):
        """Constructor method""" = test_results
        self.file_name = file_name

        self.story = list()

        self.styles = getSampleStyleSheet()
        self.styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY))

    def __create_overview(self):
        """Create overview graphic and pdf content"""
        requests = list()
        duration = list()

        for lists in


        plt.xlabel('http requests')
        plt.ylabel('response time')
        plt.xticks(range(len(duration)), requests)


        text = '<font size=18>Overview</font>'
        self.story.append(Paragraph(text, self.styles["Heading2"]))
        picture = Image('overview.png', 6*inch, 4*inch, hAlign='LEFT')

        text = '<font size=12>Lorem ipsum dolor sit amet, <br/>\
        consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt \
        ut labore et dolore magna aliquyam erat, sed diam voluptua.<br/>\
        At vero eos et accusam et justo duo dolores et ea rebum. <br/>\
        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum \
        dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing \
        elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore \
        magna aliquyam erat, sed diam voluptua. <br/>\
        At vero eos et accusam et justo duo dolores et ea rebum. <br/>\
        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum \
        dolor sit amet.</font>'
        self.story.append(Paragraph(text, self.styles["Normal"]))

        self.story.append(Spacer(1, 192))

    def __create_round_trips(self):
        """Create round trip graphics and pdf content"""

        for lists in
            text = '<font size=18>%s: %s HTTP/1.1</font>' % (lists[3],
            self.story.append(Paragraph(text, self.styles["Heading2"]))

            text = '<font size=12>Status Code: %s</font>' % lists[2]
            self.story.append(Paragraph(text, self.styles["Normal"]))
            self.story.append(Spacer(1, 12))

            file_name = 'request_' + str(lists[0]) + '.png'
            title = 'Request Circle (' + str(lists[1]) + ' sec.)'
            plt.figure(figsize=(4, 4))
            values = lists[5]
            labels = ['Request', 'Server', 'Response']
            plt.pie(values, labels=labels)

            picture = Image(file_name, 2.5*inch, 2.5*inch, hAlign='LEFT')

            self.story.append(Spacer(1, 24))

    def create_pdf(self, report_title, author, base_url, margins):
        """Create PDF document"""

        formatted_time = time.ctime()
        pdf = SimpleDocTemplate(self.file_name,

        # Add headline, created date/time and author
        text = '<font size=24>%s</font>' % report_title
        self.story.append(Paragraph(text, self.styles["Heading1"]))
        self.story.append(Spacer(1, 12))

        text = '<font size=10>%s<br/>by %s<br/>%s</font>' % (formatted_time,
        self.story.append(Paragraph(text, self.styles["Italic"]))
        self.story.append(Spacer(1, 24))

        # Add overview

        # Add results

        # Create and store report

if __name__ == '__main__':
    # some test result data
    data = ([1, 0.5, 200, 'GET', '/api/getUser', [40, 40, 20]],
            [2, 0.75, 200, 'POST', '/api/createUser', [50, 30, 20]],
            [3, 0.25, 404, 'PUT', '/api/updateUser', [50, 25, 25]],
            [4, 2, 301, 'GET', '/api/Logout', [25, 50, 25]],
            [5, 0.25, 200, 'POST', '/api/Login', [45, 35, 20]])

    # create report
    CREATE = CreateTestReport(data, 'TestReport.pdf')
    CREATE.create_pdf('Example Test Report',
                      [20, 20, 20, 10])

You can call it simply by

# execute without compile
$ python -B

Create screenshot with Python Selenium Webdriver

The following example show how easy you could make screenshots with Python Selenium Webdriver.



# -*- coding: utf-8 -*-

import unittest
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException

from MyLibrary import MyLibrary

class Example(unittest.TestCase):
    """Example class for screen shot"""

    def setUp(self):
        self.driver = webdriver.Firefox()

    def test_something(self):

        elem_id = 'is_not_there'

            elem = self.driver.find_element_by_id(elem_id)
        except NoSuchElementException:
            MyLibrary.save_screenshot_picture(self.driver, elem_id)

    def tearDown(self):

if __name__ == '__main__':

The could be used on different places.

# -*- coding: utf-8 -*-

import os
import datetime
import time

class MyLibrary(object):
    """Simple screenshot class"""

    def get_date_time():
        """Return date_time"""
        dt_format = '%Y%m%d_%H%M%S'
        return datetime.datetime.fromtimestamp(time.time()).strftime(dt_format)

    def save_screenshot_picture(driver, file_name):
        """Make screenshot and save"""

        date_time = MyLibrary.get_date_time()
        current_location = os.getcwd()
        screenshot_folder = current_location + '/screenshot/'

        if not os.path.exists(screenshot_folder):

        picture = screenshot_folder + file_name + ' ' + date_time + '.png'

After running the test you should see the folder “/screenshot” with the picture.

Postgres for Mac OS X

On Mac OS X you can use to install and use PostgreSQL. It really is the easiest way to get started with PostgreSQL on the Mac!


  1. Download from website
  2. Drag’n’Drop into /Applications folder
  3. Start


The app also provides additional command-line tools (e.q. psql). To use these tools, you need to add the directory to the path.

# create .bash_profile in home directory (if not exists)
$ touch ~/.bash_profile

# add directory path 
$ vim ~/.bash_profile
export PATH=$PATH:/Applications/


Now you can use it! If you want to use tools like pgAdmin, just connect without password.

Prettify JSON with Python

If you are testing REST , it happens that you need to prettify JSON. With Python you do not need any other tool except your terminal!


{"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossList":{"GlossEntry":{"ID":"SGML","SortAs":"SGML","GlossTerm":"Standard Generalized Markup Language","Acronym":"SGML","Abbrev":"ISO 8879:1986","GlossDef":{"para":"A meta-markup language, used to create markup languages such as DocBook.","GlossSeeAlso":["GML","XML"]},"GlossSee":"markup"}}}}}

Now open you terminal…

# Using module json.tool to prettify and validate
$ cat example.json | python -m json.tool
    "glossary": {
        "GlossDiv": {
            "GlossList": {
                "GlossEntry": {
                    "Abbrev": "ISO 8879:1986",
                    "Acronym": "SGML",
                    "GlossDef": {
                        "GlossSeeAlso": [
                        "para": "A meta-markup language, used to create markup languages such as DocBook."
                    "GlossSee": "markup",
                    "GlossTerm": "Standard Generalized Markup Language",
                    "ID": "SGML",
                    "SortAs": "SGML"
            "title": "S"
        "title": "example glossary"

JUnit report with Python Webdriver

To create JUnit reports, you just need the python library xmlrunner. This is needed for the integration with build server like Jenkins.


# example installation with pip
$ sudo pip install xmlrunner


Just replace for example:



from xmlrunner import xmlrunner

""" code for test suite ... """

xmlrunner.XMLTestRunner(verbosity=2, output='reports').run(test_suite)


On Jenkins add a new “post-build” action and select the Publish JUnit test result report. Add now the string “reports/*.xml” into the “Test report XMLs” field.

DDT with Python Selenium

DDT (Data-driven Testing) with Python Selenium Webdriver is very easy! DDT becomes very useful if you have test cases that contains the same test steps. All values could outsourced into files or databases. This tutorial use CSV files.


  • Python installed
  • selenium and ddt library installed


The folder structure for this tutorial looks like:

├── data
│   └── scenario_a.csv
├── library
│   ├──
│   └──
├── scenarios
│   ├──
│   └──

Into folder “data” we store the csv files. The packages “library” include a function to read the specific csv files and the package “scenarios” include the test cases. The test suite is on root folder.

#!/usr/bin/env python
# -*- coding: utf8 -*-

import unittest

from scenarios.scenario_a import TestScenarioA

# load test cases
scenario_a = unittest.TestLoader().loadTestsFromTestCase(TestScenarioA)

# create test suite
test_suite = unittest.TestSuite([scenario_a])

# execute test suite

Into the “” we add all test cases provided by scenario package.

data folder


The CSV stores the test data that we supplied to the @data decorator of test case.

library package

#!/usr/bin/env python
# -*- coding: utf8 -*-

__author__ = 'lupin'
#!/usr/bin/env python
# -*- coding: utf8 -*-

import csv

def get_csv_data(csv_path):
    read test data from csv and return as list

    @type csv_path: string
    @param csv_path: some csv path string
    @return list
    rows = []
    csv_data = open(str(csv_path), "rb")
    content = csv.reader(csv_data)

    # skip header line
    next(content, None)

    # add rows to list
    for row in content:

    return rows

Just for read the csv and return the values as a list.

scenarios package

#!/usr/bin/env python
# -*- coding: utf8 -*-

__author__ = 'lupin'
#!/usr/bin/env python
# -*- coding: utf8 -*-

import unittest

from selenium import webdriver
from ddt import ddt, data, unpack

from library.GetData import get_csv_data

class TestScenarioA(unittest.TestCase):
    """ inheriting the TestCase class"""

    def setUpClass(cls):
        """test preparation"""
        cls.driver = webdriver.Firefox()
        cls.driver.set_window_size(450, 500)

    def test_search(self, target_url, elem_name, search_value):
        """test case for scenario a"""
        driver = self.driver

        btn_elem = driver.find_element_by_id('search-toggle')

        input_elem = driver.find_element_by_name(elem_name)

    def tearDownClass(cls):
        """clean up"""

Test case with @ddt (for classes), @data and @unpack (for methods) decorators.

  • @data take the arguments from csv file
  • @unpack unpacks tuples or lists into multiple arguments

The test_search() method accepts the arguments, which will be mapped to the tuple values by ddt.


$ python -B