Sahi Pro - Playback on Desktop
abstract
This section describes various ways of playing back Sahi scripts.
Playback via Editor (new in 6.0.0)
The enhanced Editor (available from v6.0.0) allows the user to playback scripts, suites, data driven suites and scenarios from a web interface.The user can choose to playback on a single machine or distribute across machines. All command line scenarios (testrunner, drun, drun_different_master) can be tested from the Editor Playback UI.
One can also generate ANT xml content for various scenarios from the Playback UI. The xml content needs to be copied and saved in an xml file. The XML file can then be run through ANT.
Example: If we copy the generated ANT xml content to demo.xml, one can launch the ANT target as ant -f demo.xml
Please refer to the Playback section of the Editor documentation for more details on the Playback UI.
Playback via Sahi Controller
To playback via Controller, go to the Playback tab, choose the script to play and click "Play".As the script executes, Sahi will display the steps executed and also log them into a database.
You can view the logs by clicking on "Logs" link at the bottom of the Controller.
Playback via testrunner
testrunner.bat
and testrunner.sh
files allow running scripts from the command line.- Open a command prompt and cd to sahi/userdata/bin
(or click on the "Bin" link on the Sahi Dashboard)
- Run testrunner.bat to see the syntax
D:\sahi_pro\userdata\bin>testrunner
--
Usage: testrunnerFile path is relative to userdata/scripts
Multiple browsers can be specified using +. Eg. ie+firefox
tags are used only if the input suite is a csv file
--
Example:
testrunner demo/demo.suite http://sahi.co.in/demo/ firefox
testrunner demo/sahi_demo.sah http://sahi.co.in/demo/ ie
testrunner demo/sahi_demo.sah http://sahi.co.in/demo/ ie+firefox
testrunner demo/testcases/testcases_sample.csv http://sahi.co.in/demo/ ie "(user||admin)&&medium"
testrunner demo/ddcsv/test.dd.csv http://sahi.co.in/demo/ ie "(user||admin)&&medium"
--
D:\sahi_pro\userdata\bin> -
To run your suite, use,
You can specify the browser you want to run it on.C:\sahi\userdata\bin>testrunner.bat myapp.suite http://myappdomain/startpage.jsp firefox
-
Parameters other than suite, startURL and browserType can be modified by editing
testrunner.bat
-
To set the number of parallel browsers to execute the script in, modify
To run in one thread only, ie. to run scripts one after the other sequentially, useSET THREADS=5
SET THREADS=1
-
Sahi normally opens and closes the browser for each script.
To execute all scripts sequentially without closing and reopening browsers, use
SET THREADS=1 SET SINGLE_SESSION=true
-
Sahi, by default, writes logs to the database only.
To force it to write logs to the filesystem, use
html and xml logs will be created in theSET LOGS_INFO=html,xml
sahi/userdata/logs/playback
folder
To force it to write logs to specific file locations, use,
SET LOGS_INFO=html:D:/reports/htmllogs/,xml:D:/reports/xmllogs/
Output formats available are html,xml,junit,excel
The format to specify LOGS_INFO is
reportType1:filePath1,reportType2,reportType3:filePath3,...
-
When a Sahi suite is run, some scripts may fail because of environment reasons or due to browser crashes.
A natural next step for testers is to verify the failure by running the script again.
Sahi now automatically creates a suite of all failed scripts. The name of the failed suite has_failed
appended to the filename.
For example if you ran a suiteadmin/admin.suite
failed suite will be created asadmin/admin_failed.suite
Foradmin/admin.dd.csv
, it will beadmin/admin_failed.dd.csv
This failed suite file can then be run to verify only the failed testcases.
-
Sahi can send out emails at the end of a suite run.
Set the following properties in testrunner.bat file.
Email specific parameters like username, password etc. are defined inSET SEND_EMAIL_REPORT=true SET EMAIL_TRIGGER=success,failure SET EMAIL_PROPERTIES=email.properties SET EMAIL_PASSWORD_HIDDEN=true
sahi/userdata/bin/email.properties
file.
-
To set the number of parallel browsers to execute the script in, modify
info
Browser names are defined in sahi/userdata/browser-types-xml
testrunner.bat and testrunner.sh use the TestRunner java class internally.
testrunner.bat and testrunner.sh use the TestRunner java class internally.
Distributed playback via drun
Sahi Pro can distribute and run tests on multiple machines (nodes).
The machine which distributes the scripts and manages the distributed run is referred to as the
Master
. All Dashboard logs are visible from the Master
. The other machines are called Slaves
. Any machine in the distributed environment can serve as the Master
. The machine which launches the distributed run is referred to as the Initiator
.Sahi provides a couple of batch files -
drun.bat
and drun_different_master.bat
(and equivalent shell scripts). They allow running test scripts on multiple machines from the command line.-
drun.bat
should be used when theMaster
andInitiator
are the same machine.
-
drun_different_master.bat
should be used when theMaster
is different from theInitiator
.
danger
Please ensure that the same SahiPro version has been installed on all the machines participating in the distributed run.
drun
- Sahi distributes the scripts to different machines based on their capacity and availability of browsers. Please note that Sahi should be running on each of the nodes. Also, on each machine, the browser paths must be correctly specified.
- Open
sahi_pro/userdata/bin/drun.bat
(drun.sh on linux) using any editor. -
SOURCE_SCRIPTS_PATH points to the folder that contains ALL the scripts to be run. It should be relative to
sahi_pro/userdata
folder OR it should be an absolute path.
For Windows:
For Linux:SET SOURCE_SCRIPTS_PATH=scripts
export SOURCE_SCRIPTS_PATH=scripts
-
Configure the NODES variable to contain all the machines on which the tests should run. The command to set the nodes in Windows and Linux respectively are:
For Windows:
For Linux:SET NODES=localhost:9999,othermachine:9999,thirdmachine:9999
export NODES=localhost:9999,othermachine:9999,thirdmachine:9999
info The nodes may or may not include theMaster
machine (localhost). If theMaster
machine is not included, scripts will not be run on theMaster
.
warning "othermachine" and "thirdmachine" are placeholders to illustrate usage. Replace them with actual machine names. The nodes can contain 1 or more machines.
-
When a Sahi suite is run, some scripts may fail because of environment reasons or due to browser crashes.
A natural next step for testers is to verify the failure by running the script again.
Sahi now automatically creates a suite of all failed scripts. The name of the failed suite has_failed_<browsertype>
appended to the filename.
For example if you ran a suiteadmin/admin.suite
in Chrome, failed suite will be created asadmin/admin_failed_chrome.suite
Foradmin/admin.dd.csv
, it will beadmin/admin_failed_chrome.dd.csv
This failed suite file can then be run to verify only the failed scripts.
-
Sahi can send a mail summarizing the playback. The variable SEND_EMAIL_REPORT can be set as true or false. If true, Sahi sends the mail.
For Windows:
For Linux:SET SEND_EMAIL_REPORT=true
export SEND_EMAIL_REPORT=true
-
Sahi can send a mail based on success or failure or in both the cases. This condition can be set using the EMAIL_TRIGGER variable.
For Windows:
For Linux:SET EMAIL_TRIGGER=success,failure
export EMAIL_TRIGGER=success,failure
- The relative path to the properties file containing information about the mail such as sender, recipient, host, port etc. can be set using the EMAIL_PROPERTIES variable.
- Passwords can be hidden or shown from the console and logs by toggling the EMAIL_PASSWORD_HIDDEN variable.
It can be set as true or false. If true, Sahi hides the password from Sahi console and logs. -
Custom fields can be passed to the scripts using the CUSTOM_FIELDS variable. More than one custom field can be passed. Replace the custom field keys and values as appropriate.
For Windows:
For Linux:SET CUSTOM_FIELDS=-customField customValue -anotherCustomField "another value"
export CUSTOM_FIELDS=-customField customValue -anotherCustomField "another value"
info Specifying custom fields is not mandatory.
-
The end user can pass a specific id for each suite/script run using the USER_DEFINED_ID variable.
For Windows:
For Linux:SET USER_DEFINED_ID=-userDefinedId "Some Id"
export USER_DEFINED_ID=-userDefinedId "Some Id"
Replace the value "Some Id" as appropriate.
warning The key should remain as userDefinedId.
info Specifying user defined id is not mandatory.
-
Sahi can set offline logs to be generated in
xml
,html
,junit
,tm6
andexcel
types. The default type ishtml
.
For Windows:
For Linux:SET LOGS_INFO=xml,html
export LOGS_INFO=xml,html
- To execute the scripts using drun.bat, open a command window and go to the
<sahi_home>/userdata/bin
directory. This can also be done easily by clicking the bin link on the dashboard. - Run drun.bat to see the syntax.
D:\sahi_pro\userdata\bin>drun
--
NOTE: Use this batch file only if this machine is same as the Master machine. Else, use drun_different_master.bat instead.
--
Usage: drunFile path is relative to userdata/scripts
Multiple browsers can be specified using +. Eg. ie+firefox
tags are used only if the input suite is a csv file
--
Example:
drun demo/demo.suite http://sahi.co.in/demo/ firefox
drun demo/sahi_demo.sah http://sahi.co.in/demo/ ie
drun demo/sahi_demo.sah http://sahi.co.in/demo/ ie+firefox
drun demo/testcases/testcases_sample.csv http://sahi.co.in/demo/ ie "(user||admin)&&medium"
drun demo/ddcsv/test.dd.csv http://sahi.co.in/demo/ ie "(user||admin)&&medium"
--
D:\sahi_pro\userdata\bin>
In the usage above,
-
The first argument is the suite or script path. This path is always relative to the path set in SOURCE_SCRIPTS_PATH.
-
The second argument is the Start URL.
-
The third argument is the Browser Type.
-
Tags (the fourth argument) will be used only when running a data driven suite (.dd.csv) or a CSV as suite with tags.
Tags are specified to run specific scripts/testcases from the suite.
eg. If tags are specified as "(user||admin)&&medium", all the scripts which have 'medium' tag and 'admin' or 'user' (or both) tag will be run
-
The first argument is the suite or script path. This path is always relative to the path set in SOURCE_SCRIPTS_PATH.
drun_different_master
This should be used when theMaster
is different from the Initiator
.A typical case where you would use this or the ANT equivalent, is when you have a continuous integration build server that is not capable of launching any UI.
The build server will contain all the scripts but since it cannot launch any UI, it cannot run Sahi tests. In such a case, Sahi can be run on other machines and
drun_different_master
can be used on the integration build server to trigger the Sahi tests on these machines.drun_different_master
does the following.-
It first syncs the contents of scripts folder from the
Initiator
to a staging folder on theMaster
.
-
It then distributes the scripts from the staging folder of the
Master
to various nodes and runs the tests.
-
If offline logs are requested, the logs are copied over from the
Master
to theInitiator
after the run is over.
drun_different_master
in more detail.- Sahi (running on the
Master
) distributes the scripts to different machines based on their capacity and availability of browsers. Please note that Sahi should be running on each of the nodes. Also, on each machine, the browser paths must be correctly specified.
info Sahi does not need to be installed on theInitiator
machine.
-
The following directory structure is assumed on the
Initiator
machine. The requisite files like ant-sahi.jar should be copied over from a SahiPro installation. The directory structure of the files is the same in the SahiPro installation.
<TOP_LEVEL_FOLDER> lib ant-sahi.jar logs userdata bin drun_different_master.bat setjava.bat email.properties scripts <ALL YOUR SCRIPT FOLDERS AND SCRIPTS>
- Open
<sahi_home>/userdata/bin/drun_different_master.bat
(drun_different_master.sh on linux) using any Editor. - MASTER_HOST should point to the Master machine.
For Windows:
For Linux:SET MASTER_HOST=machine2
export MASTER_HOST=machine2
danger Make sure you change "machine2" to the actual machine name
-
INITIATOR_ORIGIN_FOLDER is the path to the base folder containing the various scripts. With the above assumed directory structure, it would point to
<TOP_LEVEL_FOLDER>/userdata/scripts
.
info The default value need not be changed.
-
MASTER_STAGING_PATH refers to the Staging folder on the Master machine to which the contents of INITIATOR_ORIGIN_FOLDER will be first synced to. Distribution of scripts will happen from MASTER_STAGING_PATH.
info The default value need not be changed.
-
Configure the NODES variable to contain all the machines on which the tests should run. The command to set the nodes in Windows and Linux respectively are:
For Windows:
For Linux:SET NODES=machine2:9999,machine3:9999,machine4:9999
export NODES=machine2:9999,machine3:9999,machine4:9999
info The nodes may or may not include theMaster
machine. If theMaster
machine is not included, scripts will not be run on theMaster
.
danger Do not include localhost (theInitiator
) here since Sahi may not be installed on theInitiator
. If you want localhost as one of the nodes, run drun instead.
warning "machine2", "machine3" and "machine4" are placeholders to illustrate usage. Replace them with actual machine names. The nodes can contain 1 or more machines.
-
When a Sahi suite is run, some scripts may fail because of environment reasons or due to browser crashes.
A natural next step for testers is to verify the failure by running the script again.
Sahi now automatically creates a suite of all failed scripts. The name of the failed suite has_failed_<browsertype>
appended to the filename.
For example if you ran a suiteadmin/admin.suite
in Chrome, failed suite will be created asadmin/admin_failed_chrome.suite
Foradmin/admin.dd.csv
, it will beadmin/admin_failed_chrome.dd.csv
This failed suite file can then be run to verify only the failed scripts.
-
Sahi can send a mail summarizing the playback. The variable SEND_EMAIL_REPORT can be set as true or false. If true, Sahi sends the mail.
For Windows:
For Linux:SET SEND_EMAIL_REPORT=true
export SEND_EMAIL_REPORT=true
-
Sahi can send a mail based on success or failure or in both the cases. This condition can be set using the EMAIL_TRIGGER variable.
For Windows:
For Linux:SET EMAIL_TRIGGER=success,failure
export EMAIL_TRIGGER=success,failure
- The relative path to the properties file containing information about the mail such as sender, recipient, host, port etc. can be set using the EMAIL_PROPERTIES variable.
- Passwords can be hidden or shown from the console and logs by toggling the EMAIL_PASSWORD_HIDDEN variable.
It can be set as true or false. If true, Sahi hides the password from Sahi console and logs. -
Custom fields can be passed to the scripts using the CUSTOM_FIELDS variable. More than one custom field can be passed. Replace the custom field keys and values as appropriate.
For Windows:
For Linux:SET CUSTOM_FIELDS=-customField customValue -anotherCustomField "another value"
export CUSTOM_FIELDS=-customField customValue -anotherCustomField "another value"
info Specifying custom fields is not mandatory.
-
The end user can pass a specific id for each suite/script run using the USER_DEFINED_ID variable.
For Windows:
For Linux:SET USER_DEFINED_ID=-userDefinedId "Some Id"
export USER_DEFINED_ID=-userDefinedId "Some Id"
Replace the value "Some Id" as appropriate.
warning The key should remain as userDefinedId.
info Specifying user defined id is not mandatory.
-
Sahi can set offline logs to be generated in
xml
,html
,junit
,tm6
andexcel
types. The default type ishtml
. These logs will be generated on theMaster
and pulled back to theInitiator
, since the user would want to see the logs on theInitiator
.
The following set of commands illustrate how HTML and XML logs can be generated.
For Windows:
For Linux:SET DATESTAMP=%DATE:~10,4%_%DATE:~4,2%_%DATE:~7,2% SET TIMESTAMP=%TIME:~0,2%_%TIME:~3,2%_%TIME:~6,2% SET DATEANDTIME=%DATESTAMP%_%TIMESTAMP% SET MASTER_HTMLLOGS_DIR=logs/temp/html/%DATEANDTIME% SET MASTER_XMLLOGS_DIR=logs/temp/xml/%DATEANDTIME% SET LOGS_INFO=html:%MASTER_HTMLLOGS_DIR%,xml::%MASTER_XMLLOGS_DIR% SET INITIATOR_OUTPUT_HTMLLOGS_DIR=%TOP_LEVEL_FOLDER%/logs/html/%DATEANDTIME% SET INITIATOR_OUTPUT_XMLLOGS_DIR=%TOP_LEVEL_FOLDER%/logs/xml/%DATEANDTIME%
export MASTER_HTMLLOGS_DIR=logs/temp/html export MASTER_XMLLOGS_DIR=logs/temp/xml export LOGS_INFO=html:$MASTER_HTMLLOGS_DIR,xml:$MASTER_XMLLOGS_DIR export INITIATOR_OUTPUT_HTMLLOGS_DIR=$TOP_LEVEL_FOLDER/logs/html export INITIATOR_OUTPUT_XMLLOGS_DIR=$TOP_LEVEL_FOLDER/logs/xml
info The default values can be left as is.
- To execute the scripts using drun_different_master.bat, open a command window and go to the
<sahi_home>/userdata/bin
directory. This can also be done easily by clicking the bin link on the dashboard. - Run drun_different_master.bat to see the syntax.
D:\sahi_pro\userdata\bin>drun_different_master.bat
--
NOTE: Use this batch file only if this machine is different from the Master machine. Else, use drun.bat instead.
--
Usage: drun_different_master.batFile path is relative to userdata/scripts
Multiple browsers can be specified using +. Eg. ie+firefox
tags are used only if the input suite is a csv file
--
Example:
drun_different_master.bat demo/demo.suite http://sahi.co.in/demo/ firefox
drun_different_master.bat demo/sahi_demo.sah http://sahi.co.in/demo/ ie
drun_different_master.bat demo/sahi_demo.sah http://sahi.co.in/demo/ ie+firefox
drun_different_master.bat demo/testcases/testcases_sample.csv http://sahi.co.in/demo/ ie "(user||admin)&&medium"
drun_different_master.bat demo/ddcsv/test.dd.csv http://sahi.co.in/demo/ ie "(user||admin)&&medium"
--
D:\sahi_pro\userdata\bin>
In the usage above,
-
The first argument is the suite or script path. This path is always relative to the path set in SOURCE_SCRIPTS_PATH.
-
The second argument is the Start URL.
-
The third argument is the Browser Type.
-
Tags (the fourth argument) will be used only when running a data driven suite (.dd.csv) or a CSV as suite with tags.
Tags are specified to run specific scripts/testcases from the suite.
eg. If tags are specified as "(user||admin)&&medium", all the scripts which have 'medium' tag and 'admin' or 'user' (or both) tag will be run.
-
The first argument is the suite or script path. This path is always relative to the path set in SOURCE_SCRIPTS_PATH.
Playback via ANT
warning
demo.xml has been removed from the Sahi installation. Use the Editor Playback UI to generate the ANT xml content and create an xml with that content in your
<SAHI_INSTALLATION_FOLDER>
.Playback on a single machine
One can now use the Editor Playback UI to generate the ANT xml automatically. Refer here for details on the UI.- Choose the
Run in parallel
option. - Click the
Create Ant Target
button
This will generate the contents of an ANT xml file. If you wish to add a Retry target as well, click on the
Show Retry Target
button on the next window.Once the ANT xml content is generated, copy the content to an empty file and save the file as an xml, say
demo.xml
in your <SAHI_INSTALLATION_FOLDER>
.You can now run the default ANT target in the xml file as
ant -f demo.xml
NOTE: The main target is the default target, so you do not need to specify the target when running the xml.
The above UI would generate an ANT xml that looks like the following.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
 Custom fields - add, edit or remove as required -->
<customfield key="user" value="test"/>
<customfield key="mask_password" value="secret"/>
<!-- Offline reports -->
<report type="html" logdir="d:/temp/html"/>
<report type="xml"/>
</sahi>
<antcall target="failsahiie"/>
</target>
<target name="failsahiie" depends="set_properties_ie" if="sahi.failed.${browser}">
<fail message="Sahi tests failed on browser ${browser}!"/>
</target>
</project>
info
If you choose the
Run sequentially in single browser session
option, singlesession attribute would be set to true. Everything else remains the same.danger
Please NOTE:
The above XML content is for illustration purposes only. Use the Editor UI to generate the ANT xml content and create an xml with that content in your
The above XML content is for illustration purposes only. Use the Editor UI to generate the ANT xml content and create an xml with that content in your
<SAHI_INSTALLATION_FOLDER>
.Let us look at the important attributes used in the ANT xml target (specified in bold above).
Attributes and their description
info
taskdef classpath is relative to where ant is run from.
| scriptDir | Specifies the path to the scripts folder. Can be the absolute path or a path relative to the userdata folder |
| scriptName | Relative path to the script/suite/data driven suite. It is relative to scriptDir. |
| browser | The browser on which the suite file plays back |
| baseurl | Specifies URL which is the starting URL for all the scripts in the suite |
| host | Hostname of the server where Sahi is running (Can be IP as well) |
| port | Port on which Sahi is running |
| threads | Number of simultaneous browser instances on which sahi tests will be run. |
| singlesession | Since this is a parallel run, it will be "false". If "true", runs all scripts in a single browser without closing it between scripts. |
| sendemail | Takes "true" or "false". If "true", Sahi sends an email summarizing the playback. |
| emailtrigger | Takes "success" or "failure" or "success,failure". Triggers email in case of SUCCESS, FAILURE and both cases respectively. |
| emailproperties | Path to the properties file that contains details about the mail viz. information about the sender, receiver, host, port, subject, content etc. Path can be the absolute path or relative to where this ant target is run from. |
| failedsuite | Relative path to the failed suite. It is relative to scriptDir. It is of the form <suitename>_failed_<browser>.suite |
| tags | Tags should be specified only for dd.csv and .csv suites. Tags are specified so that it is easy to choose which scripts/testcases to run. eg. in the target above, all the scripts which have 'medium' tag and 'admin' or 'user' (or both) tag will be run. |
| userdefinedid | The unique id that the user can pass to differentiate a suite from others. This is an optional attribute. |
| customfield | Custom fields can be passed to the scripts using the customfield attribute. More than one custom field can be passed, by specifying additional customfield entries. Replace the custom field keys and values as appropriate. This is an optional attribute. |
| report | Sahi can set offline logs to be generated in xml , html , junit , tm6 and excel types. The default type is html . Add separate entries for each report type as required. This is an optional attribute. |
Command to execute the above ANT target
ant -f demo.xml
Distributed playback via ANT
Distributed playback can be achieved through ANT targets as well.These targets can be used from Jenkins to distribute the scripts of a suite on multiple machines.
The machine which distributes the scripts and manages the distributed run is referred to as the
Master
. All logs are visible from the Master
. The other machines are called Slaves
. Any machine in the distributed environment can serve as the Master
. The machine which launches the distributed run is referred to as the Initiator
.Distributed run on the same Master
Use the Editor Playback UI to generate the ANT xml content.In the Editor Playback UI,
- Choose
Run distributed (multiple machines)
option. - Leave the
Use Different Master
checkbox unchecked. - Click on
Create Ant Target
button.
This will generate the contents of an ANT xml file. If you wish to add a Retry target as well, click on the
Show Retry Target
button on the next window.Once the ANT xml content is generated, copy the content to an empty file and save the file as an xml, say
demo.xml
in your <SAHI_INSTALLATION_FOLDER>
.You can now run the default ANT target in the xml file as
ant -f demo.xml
NOTE: The main target is the default target, so you do not need to specify the target when running the xml.
The above UI would generate an ANT xml that looks like the following.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
/**
* Copyright Tyto Software Pvt. Ltd.
*/
-->
<project name="demo" default="drunie">
<taskdef name="sahi" classname="net.sf.sahi.ant.RunSahiTask" classpath="lib/ant-sahi.jar"/>
<taskdef name="sahid" classname="in.co.sahi.ant.DAntRunner" classpath="lib/ant-sahi.jar"/>
<target name="set_timestamp">
<tstamp>
<format property="ts" pattern="yyyy_MM_dd_HH_mm_ss" locale="en, IN"/>
</tstamp>
</target>
<target name="set_properties_common">
<property name="scriptDir" value="D:/sahi/userdata/scripts/"/>
<property name="host" value="localhost"/>
<property name="port" value="9999"/>
<property name="runId" value="${ts}"/>
</target>
<target name="set_properties_ie" depends="set_properties_common">
<property name="scriptName" value="demo/ddcsv/test.dd.csv"/>
<property name="browser" value="ie"/>
<property name="sendemail" value="true"/>
<property name="emailtrigger" value="success,failure"/>
<property name="emailproperties" value="D:\sahi\userdata\config\email.properties"/>
<property name="emailPasswordHidden" value="true"/>
<property name="baseURL" value="http://sahi.co.in/demo/"/>
<property name="failedSuite" value="demo/ddcsv/test_failed_${browser}.dd.csv"/>
</target>
<target name="drunie" depends="set_properties_ie, set_timestamp">
<sahid suite="temp/scripts/copied/${scriptName}"
sendEmail="${sendemail}"
emailtrigger="${emailtrigger}"
emailProperties="${emailproperties}"
emailPasswordHidden="${emailPasswordHidden}"
baseurl="${baseURL}"
sahihost="${host}"
sahiport="${port}"
failureproperty="sahi.failed.${browser}"
haltonfailure="false"
originalSuitePath="${scriptDir}${scriptName}"
browsertype="${browser}"
origScriptsPath="${scriptDir}"
copiedScriptsPath="temp/scripts/copied"
runId="${runId}"
ignorePattern=".*(svn|copied).*"
tags="(user||admin)&&medium"
userDefinedId="IETestRun1">
<!-- Custom fields - add, edit or remove as required -->
<customfield key="user" value="test"/>
<customfield key="mask_password" value="secret"/>
<!-- Scripts will be distributed across all the nodes. The nodes may or may not include the Master machine -->
<node host="localhost" port="9999"/>
<node host="<IP_ADDRESS>" port="9999"/>
<!-- Offline reports -->
<report type="html" logdir="d:/temp/html"/>
<report type="xml"/>
</sahid>
<antcall target="failsahiie"/>
</target>
<target name="failsahiie" depends="set_properties_ie" if="sahi.failed.${browser}">
<fail message="Sahi tests failed on browser ${browser}!"/>
</target>
</project>
danger
Please NOTE:
The above XML content is for illustration purposes only. Use the Editor UI to generate the ANT xml content and create an xml with that content in your
The above XML content is for illustration purposes only. Use the Editor UI to generate the ANT xml content and create an xml with that content in your
<SAHI_INSTALLATION_FOLDER>
.Let us look at the important attributes used in the ANT xml target (specified in bold above).
Attributes and their description
info
taskdef classpath is relative to where ant is run from.
| scriptDir | Specifies the path to the scripts folder. Can be the absolute path or a path relative to the userdata folder |
| host | Hostname of the server where Sahi is running (Can be IP as well). Leave this as localhost |
| port | Port on which Sahi is running |
| scriptName | Relative path to the script/suite/data driven suite. It is relative to scriptDir. |
| browser | The browser on which the suite file plays back |
| baseurl | Specifies URL which is the starting URL for all the scripts in the suite |
| sendemail | Takes "true" or "false". If "true", Sahi sends an email summarizing the playback. |
| emailtrigger | Takes "success" or "failure" or "success,failure". Triggers email in case of SUCCESS, FAILURE and both cases respectively. |
| emailproperties | Path to the properties file that contains details about the mail viz. information about the sender, receiver, host, port, subject, content etc. Path can be the absolute path or relative to where this ant target is run from. |
| failedsuite | Path to the failed suite file. It is relative to scriptDir. It is of the form <suitename>_failed_<browser>.suite |
| tags | Tags should be specified only for dd.csv and .csv suites. Tags are specified so that it is easy to choose which scripts/testcases to run. eg. in the target above, all the scripts which have 'medium' tag and 'admin' or 'user' (or both) tag will be run. |
| userdefinedid | The unique id that the user can pass to differentiate a suite from others. This is an optional attribute. |
| node | Each node attribute specifies a machine on which the tests should run. Add as many node entries as there are machines to run. The nodes may or may not include the Master machine (localhost). If the Master machine is not included, scripts will not be run on the Master . There can be 1 or more nodes. |
| customfield | Custom fields can be passed to the scripts using the customfield attribute. More than one custom field can be passed, by specifying additional customfield entries. Replace the custom field keys and values as appropriate. This is an optional attribute. |
| report | Sahi can set offline logs to be generated in xml , html , junit , tm6 and excel types. The default type is html . Add separate entries for each report type as required. This is an optional attribute. |
Command to execute the above ANT target
ant -f demo.xml
Distributed run on a different Master
Use the Editor Playback UI to generate the ANT xml content.In the Editor Playback UI,
- Choose
Run distributed (multiple machines)
option. - Check the
Use Different Master
checkbox and choose the Master details. - Click on
Create Ant Target
button.
This will generate the contents of an ANT xml file. If you wish to add a Retry target as well, click on the
Show Retry Target
button on the next window.Once the ANT xml content is generated, copy the content to an empty file and save the file as an xml, say
demo.xml
in your <SAHI_INSTALLATION_FOLDER>
.You can now run the default ANT target in the xml file as
ant -f demo.xml
NOTE: The main target is the default target, so you do not need to specify the target when running the xml.
The above UI would generate an ANT xml that looks like the following.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
/**
* Copyright Tyto Software Pvt. Ltd.
*/
-->
<project name="demo" default="drun_different_master">
<taskdef name="sahi" classname="net.sf.sahi.ant.RunSahiTask" classpath="lib/ant-sahi.jar"/>
<taskdef name="sahid" classname="in.co.sahi.ant.DAntRunner" classpath="lib/ant-sahi.jar"/>
<taskdef name="sahisync" classname="in.co.sahi.ant.DAntSync" classpath="lib/ant-sahi.jar"/>
<taskdef name="sahipull" classname="in.co.sahi.ant.DAntPull" classpath="lib/ant-sahi.jar"/>
<target name="set_timestamp">
<tstamp>
<format property="ts" pattern="yyyy_MM_dd_HH_mm_ss" locale="en, IN"/>
</tstamp>
</target>
<target name="set_properties_common">
<property name="scriptDir" value="D:/sahi/userdata/scripts/"/>
<property name="masterhost" value="<IP_ADDRESS>"/>
<property name="masterport" value="9999"/>
<property name="runId" value="${ts}"/>
</target>
<target name="drun_different_master" depends="set_properties_common, set_timestamp">
<sequential>
<sahisync originFolder="${scriptDir}" destFolder="temp/scripts/staging" ignorePattern=".*(svn|copied).*">
<node host="${masterhost}" port="${masterport}"/>
</sahisync>
<antcall target="drunie"/>
</sequential>
</target>
<target name="set_properties_ie" depends="set_properties_common">
<property name="scriptName" value="demo/ddcsv/test.dd.csv"/>
<property name="browser" value="ie"/>
<property name="sendemail" value="true"/>
<property name="emailtrigger" value="success,failure"/>
<property name="emailproperties" value="D:\sahi\userdata\config\email.properties"/>
<property name="emailPasswordHidden" value="true"/>
<property name="baseURL" value="http://sahi.co.in/demo/"/>
<property name="failedSuite" value="demo/ddcsv/test_failed_${browser}.dd.csv"/>
</target>
<target name="drunie" depends="set_properties_ie, set_timestamp">
<sahid suite="temp/scripts/copied/${scriptName}"
sendEmail="${sendemail}"
emailtrigger="${emailtrigger}"
emailProperties="${emailproperties}"
emailPasswordHidden="${emailPasswordHidden}"
baseurl="${baseURL}"
sahihost="${masterhost}"
sahiport="${masterport}"
failureproperty="sahi.failed.${browser}"
haltonfailure="false"
originalSuitePath="temp/scripts/staging/${scriptName}"
browsertype="${browser}"
origScriptsPath="temp/scripts/staging/"
copiedScriptsPath="temp/scripts/copied"
runId="${runId}"
ignorePattern=".*(svn|copied).*"
tags="(user||admin)&&medium"
userDefinedId="IETestRun1">
<!-- Custom fields - add, edit or remove as required -->
<customfield key="user" value="test"/>
<customfield key="mask_password" value="secret"/>
<!-- Scripts will be distributed across all the nodes. The nodes may or may not include the Master machine -->
<node host="<IP_ADDRESS>" port="9999"/>
<node host="<IP_ADDRESS>" port="9999"/>
<!-- Offline reports -->
<report type="html" logdir="logs/temp/html/${ts}"/>
<report type="xml" logdir="logs/temp/xml/${ts}"/>
</sahid>
<!-- Pull the logs from the Master onto the Initiator machine -->
<sahipull sourceHost="<IP_ADDRESS>" sourcePort="9999" originFolder="logs/temp/html/${ts}" destFolder="d:/temp/html" ignorePattern=".*(svn|copied).*"/>
<sahipull sourceHost="<IP_ADDRESS>" sourcePort="9999" originFolder="logs/temp/xml/${ts}" destFolder="logs/xml" ignorePattern=".*(svn|copied).*"/>
<antcall target="failsahiie"/>
</target>
<target name="failsahiie" depends="set_properties_ie" if="sahi.failed.${browser}">
<fail message="Sahi tests failed on browser ${browser}!"/>
</target>
</project>
danger
Please NOTE:
The above XML content is for illustration purposes only. Use the Editor UI to generate the ANT xml content and create an xml with that content.
The above XML content is for illustration purposes only. Use the Editor UI to generate the ANT xml content and create an xml with that content.
Let us look at the important attributes used in the ANT xml target (specified in bold above).
Attributes and their description
info
taskdef classpath is relative to where ant is run from.
| scriptDir | Specifies the path to the scripts folder on the Initiator machine. This can be an absolute path or a path relative to where Ant is run from |
| masterhost | Hostname of the different Master server where Sahi is running (Can be IP as well) |
| masterport | Port on which Sahi is running |
| scriptName | Relative path to the script/suite/data driven suite. It is relative to scriptDir. |
| browser | The browser on which the suite file plays back |
| baseurl | Specifies URL which is the starting URL for all the scripts in the suite |
| sendemail | Takes "true" or "false". If "true", Sahi sends an email summarizing the playback. |
| emailtrigger | Takes "success" or "failure" or "success,failure". Triggers email in case of SUCCESS, FAILURE and both cases respectively. |
| emailproperties | Path to the properties file that contains details about the mail viz. information about the sender, receiver, host, port, subject, content etc. Path can be the absolute path or relative to where this ant target is run from. |
| failedsuite | Path to the failed suite file. It is relative to scriptDir. It is of the form <suitename>_failed_<browser>.suite |
| tags | Tags should be specified only for dd.csv and .csv suites. Tags are specified so that it is easy to choose which scripts/testcases to run. eg. in the target above, all the scripts which have 'medium' tag and 'admin' or 'user' (or both) tag will be run. |
| userdefinedid | The unique id that the user can pass to differentiate a suite from others. This is an optional attribute. |
| node | Each node attribute specifies a machine on which the tests should run. Add as many node entries as there are machines to run. The nodes may or may not include the Master machine. If the Master machine is not included, scripts will not be run on the Master . There can be 1 or more nodes. |
| customfield | Custom fields can be passed to the scripts using the customfield attribute. More than one custom field can be passed, by specifying additional customfield entries. Replace the custom field keys and values as appropriate. This is an optional attribute. |
Command to execute the above ANT target
ant -f demo.xml
Email.properties file explained
Configuration file for javax.mail
If a value for an item is not provided, then system defaults will be used.| mail.smtp.starttls.enable | boolean | If true, enables the use of the STARTTLS command (if supported by the server) to switch the connection to a TLS-protected connection before issuing any login commands. Note that an appropriate trust store must configured so that the client will trust the server's certificate. |
| mail.host | String | The server to connect to. |
| mail.port | int | The server port to connect to. |
| mail.user | String | Default user name. |
| mail.password | String | Password |
| mail.smtp.auth | boolean | If true, attempt to authenticate the user using the AUTH command. |
| mail.isSSL | boolean | If true, uses SSL to connect and use the SSL port by default. |
| mail.from | String | Email address of the sender |
| mail.to | String | Email address of the sender's To field. For multiple address separate by comma(,). |
| mail.cc | String | Email address of the sender's CC field, for multiple address separate by comma(,). |
| mail.bcc | String | Email address of the sender's BCC field. for multiple address separate by comma(,). |
| mail.subject.prefix | String | The subject for the email. |
| mail.content.prefix | String | The body content for the email. |
Sample properties file
mail.smtp.starttls.enable=true
mail.host=smtp.gmail.com
mail.port=25
<EMAIL_ADDRESS>mail.password=your_password
mail.smtp.auth=true
mail.isSSL=true
<EMAIL_ADDRESS>mail.to=recepient1@gmail.com,recepient2<EMAIL_ADDRESS>mail.cc=recepient4<EMAIL_ADDRESS>mail.bcc=recepient6<EMAIL_ADDRESS>mail.subject.prefix=my subject
mail.content.prefix=my body


[]. OpenUrl vaccine URL peptide EBOV response CrossRef mice adjuvanted NP- Figure immune shown SARS-CoV- ELISPOT microspheres binding virus peptides CTL data HLA nucleocapsid study Material Table protein (mg PFU class ebola epitopes using vaccinated T-cell Supplementary microsphere PBS Science population protection vaccines Ebola cell control epitope immunity sequences survivors View Web analysis injection) levels mer responses tail HIV antigen in-vitro infection intraperitoneal possible predicted proteins sequence showed Abstract CBL/ Download Michael Nielsen TLR- active buffer coronavirus described groups individuals injection specific targeting used FREE Flow Immunology, John Text antibody based challenge infection. mouse Alessandro Morten Sette Wang affinity associated cells collected conserved containing coverage days group identified outbreak produce sars spike t-cell viral administered attack controllers delivered did different influenza maEBOV new produced severe statistically targeted vaccination versus Class Frontiers Human Open Peng Scott Sidney West Wilson acute approach beginning compared data-hide-link-title=""> data-icon-position="" figure formulation human loaded located post-challenge protective respiratory restricted solution) tab (Supplementary Buus IL- Liu M.D. MHC NP-. Ph.D. Sakebe Thomas William Yang Zaire Zhang animals development disease effective elevated evaluated exposed exposure important infected innate morbidity mortality multiple samples seen suggests vaccination. &; (a). (d). (n.s. Antibody COVID- Feldmann Geisbert Group H-D IEDB, Kasper NHPs National NetMHC NetMHCpan Nucleocapsid Paul Peters Pharma, Section Sren T-cells Tan Tang agonists amino available commonly conducted controls coverage. cytotoxic database design effect essential experiments favored https://doi.org/./fmicb... https://www.iedb.org/reference/. incorporating inline lethal making nucleocapsid. peptide. phosphoprotein plate platform popup predictions provides received regions research, risk role saw set seven significant structure sub-sequence sub-sequences suggested survival synthetic table: treated unique


Installing various packages can make your system vulnerable to any known or discovered security issues in the software. Hackers trying to gain access to your machine often can take advantage of such security holes in your system's software. For this reason, it is highly recommended to purge anything on your machine that is not required to be there.
Apache is an HTML server intended to provide users access to webpages and other resources. If the README specifically instructs you to keep Apache, steps should be taken to secure it, but if no mention is made, Apache should be removed entirely.
To check if Apache is installed on your system, you can type dpkg -l | grep apache. (the grep command is to find packages with names containing "apache")
If results come up, then Apache is likely installed. To remove Apache, purge the results that return from the above command. (For example: sudo apt-get purge apache2 apache2-bin apache2-data apache2-utils)
That should be it!


| Team A | Team B | |
|---|---|---|
| To |
| A team members | B team members | |
|---|---|---|
| To |
| A starting population | B starting population |
|---|---|
| 100 | 20000 |
Interaction count: 0
| As killed by Bs | Bs killed by As |
|---|---|
| 0 | 0 |
| Percent of As killed | Percent of Bs killed |
|---|---|
| 0% | 0% |
| B's seen by A's before A killed | A's seen by B's before B killed |
|---|---|
| 0 | 0 |


Reflecting on my web-app development process after reading The Twelve-Factor App
I recently had a conversation with a developer on the topic of bridging application development and production. From this conversation I was recommneded to have a look at The Twelve-Factor App, a high level guide for building modern, production-ready web applications. In this article I thought it would be interesting to go through each of the twelve sections and reflect on my current development process and how it follows and/or deviates from these factors. I also want to talk about the new technologies and techniques I have been learning from testdriven.io and how I hope they can benefit me on the next stage of my learning.
I. Codebase
One codebase tracked in revision control, many deploys
I have been using git to track my projects since starting Obey the Testing Goat! and have been gradually exploring many of the different features beyond a linear add
-commit
-push
loop. Using Visual Studio Code makes resolving merge conflicts very easy. On this blog I have accepted at least one pull requests from a helpful readers to correct outdated information.
The pattern I have been following for Django uses .gitignore
to keep local application settings out of the production codebase using the following logic:
from .base import *
from .production import *
try:
from .local import *
except:
pass
II. Dependencies
Explicitly declare and isolate dependencies
Using python makes this easy and I have had success running pip install -r requirements.txt
, or:
ADD ./requirements.txt /usr/src/app/requirements.txt
RUN pip install -r requirements.txt
when running Docker.
I have ran into minor dependency issues in experimenting with my personal website. I shouldn’t have been doing it this way, but I made a virtual environment with conda create
and included all of the packages in the environment in my production requirements.txt
. The Heroku build process gave me a tricky error that I was able to trace to a dependency that was failing to install on the Heroku instance.
With Python 3 there are a few different options for creating virtual environments: virtualenv
, venv
and conda
are three that I have used, and virtualenv
is a reliable tool for building webapps that I have seen used widely.
Another interesting tip from this section relates to common system tools:
Twelve-factor apps also do not rely on the implicit existence of any system tools. Examples include shelling out to ImageMagick or curl.
This is something that I have been grappling with in Docker. Tools like wget
aren’t part of “base images” and need to be intalled in the Dockerfile
or in scripts called from docker-compose.yml
.
III. Config
Store config in the environment
Heroku’s command line utilities make setting production environments very easy. I think I can improve the way I organize environment variables locally since I often have many different projects it would be easy for projects to accidentally share the same variable name. One idea I have thought about would be to have an untracked bash script that sets environment variables that I run when starting the development environment, something like:
export SECRET_KEY="my_secret_key"
export DB_URL="postgres://my_db_url"
Docker wins points again on this factor because environemnt variables can simple be defined in a development and production docker-compose-*.yml
files:
environment:
- APP_SETTINGS=project.config.DevelopmentConfig
- DATABASE_URL=postgres://postgres:postgres@users-db:5432/users_dev
- DATABASE_TEST_URL=postgres://postgres:postgres@users-db:5432/users_test
IV. Backing services
Treat backing services as attached resources
The key takeaway for backing services (databases, message/queuing systems, etc.) is:
The code for a twelve-factor app makes no distinction between local and third party services.
This factor made me think of an interesting syntax that I saw for the first time when learning the microservices architecture with docker in this Docker example that uses two Flask apps to provide 1) front-end templates and 2) API backend. When the front end calls the API backend, it does so by referencing the service name in the URL. Here is an example with PHP, but it would work similarly in any other framework:
<?php
$json = file_get_contents('http://product-service/');
$obj = json_decode($json);
$products = $obj->products;
foreach ($products as $product) {
echo "<li>$product</li>";
}
?>
This front-end code hits the backend API endpoint http://product-service/
, where product-service
is the name of a service included in docker-compose.yml
:
version: '3'
services:
product-service:
build: ./product
volumes:
- ./product:/usr/src/app
ports:
- 5001:80
website:
image: php:apache
volumes:
- ./website:/var/www/html
ports:
- 5000:80
depends_on:
- product-service
This is docker-compose file creates a network that includes both website
and product-service
that can be accessed by simply creating a URL with the name of the service in the domain. Coming back to the fourth factor, The code for a twelve-factor app makes no distinction between local and third party services, multiple docker containers can be thought of as separate services even though they may be running on the same virtual environment in either development or production, and the unique domain cooresponding to the service name seems to reinforce this concept.
This URL could also be referenced with environment variables:
getUsers() {
axios.get(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`)
.then((res) => { console.log(res); })
.catch((err) => { console.log(err); })
}
V. Build, release, run
Strictly separate build and run stages
This is an important stage in getting from development to production. It is the handoff from a local repo to a live, running web application. Here’s the process live in action:
$ git push heroku master
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 303 bytes | 303.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Python app detected
remote: -----> Installing requirements with pip
remote:
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 193.7M
remote: -----> Launching...
remote: Released v410
remote: https://briancaffey.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/briancaffey.git
cd6ce76..d8981a3 master -> master
(briancaffey) brian@archthinkpad ~/Documents/github/briancaffey/src
VI. Processes
Execute the app as one or more stateless processes
This is handled in Heroku by the Procfile. For simple Django apps on Heroku this is usually always the same one line:
web: gunicorn projectname.wsgi --log-file -
Here’s what the Django project says about using gunicorn:
When Gunicorn is installed, a gunicorn command is available which starts the Gunicorn server process. At its simplest, gunicorn just needs to be called with the location of a module containing a WSGI application object named application.
So for a typical Django project, invoking gunicorn would look like:
gunicorn myproject.wsgi
This will start one process running one thread listening on <IP_ADDRESS>:8000. It requires that your project be on the Python path; the simplest way to ensure that is to run this command from the same directory as your manage.py file.
In a Django project, the wsgi.py
file in the main folder of the project root directory has the following contents:
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "brianblog.settings")
application = get_wsgi_application()
VI. Port binding
Export services via port binding
This is an area that I’m trying to learn more about. I feel like I have a pretty good grasp of what is going on regarding port binding in the microservice architecture with docker I have seen.
From the docker-compose docs, the “short syntax” for mapping ports between hosts and containers is HOST:CONTAINER
:
Either specify both ports (HOST:CONTAINER), or just the container port (a random host port will be chosen).
The following are examples of how this could work:
ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "<IP_ADDRESS>:8001:8001"
- "<IP_ADDRESS>:5000-5010:5000-5010"
- "6060:6060/udp"
VIII. Concurrency
Scale out via the process model
This is an important area, but it is something I haven’t had to be aware of since the apps I have developed don’t require scaling processes. I belive that Heroku makes this fairly simple by allowing you to increase the number of web or worker processes through the CLI:
heroku ps:scale web=1 worker=5
I haven’t covered Part 5 of testdriven.io yet, but it has a section on Elastic Load Balancing with EC2 which should cover this area.
Twelve-factor app processes should never daemonize or write PID files. Instead, rely on the operating system’s process manager to manage output streams, respond to crashed processes, and handle user-initiated restarts and shutdowns.
I have been learning more about systemd
and customizing
IX. Disposability
Maximize robustness with fast startup and graceful shutdown
The twelve-factor app’s processes are disposable, meaning they can be started or stopped at a moment’s notice. This facilitates fast elastic scaling, rapid deployment of code or config changes, and robustness of production deploys.
I have used some Heroku tools to start and stop web workers, and docker commands make this factor fairly easy to do correctly. Here’s an excerpt from Century Link about the docker stop
command:
The docker stop command attempts to stop a running container first by sending a SIGTERM signal to the root process (PID 1) in the container. If the process hasn’t exited within the timeout period a SIGKILL signal will be sent.
X. Dev/prod parity
Keep development, staging, and production as similar as possible
This is exactly why I’m so interested in using Docker.
In one of my personal projects I did with Heroku I was relying on a feature of Postgres that is not available in sqlite3, the default database that comes with Django. This produced friction that I wouldn’t have had to deal with if I was using Docker. I could have set up a local postgres server, but it would have been much easier to run a docker container that ran the server.
XI. Logs
Treat logs as event streams
A twelve-factor app never concerns itself with routing or storage of its output stream. It should not attempt to write to or manage logfiles. Instead, each running process writes its event stream, unbuffered, to stdout. During local development, the developer will view this stream in the foreground of their terminal to observe the app’s behavior.
Running heroku log
has been helpful in debugging deployment issues.
Docker also produces helpful logs for all the containers currently running.
XII. Admin processes
Run admin/management tasks as one-off processes
One-off admin processes should be run in an identical environment as the regular long-running processes of the app. They run against a release, using the same codebase and config as any process run against that release. Admin code must ship with application code to avoid synchronization issues.
This seems to be true about the way I run admin processes on my Django apps.


HOME
TEAM
PROJECTS
CONTRIBUTORS
OPPORTUNITIES
CONTACT US
Hacktoberfest 2021
Hacktoberfest encourages participation in giving back to the open source community by completing pull requests, participating in events, and donating to open source projects.
More Details


TECNO Camon 12 first impressions and where to buy– Techjaja
by pages/Techjaja/291142381017322As is the case, TECNO followed up with a successor to the coveted Camon 11 and now we have the Camon 12.
The Camon series is its line of smartphones best known for their photography prowess and in recent times, they’ve tended to deviate towards being selfie-focused. Not to say the back camera is not capable but imagine 16MP for a selfie camera, which is the same as the rear one, only that the latter carries more lenses of 8MP and 2MP for wide-angle shots and macro photography to form the triple AI camera.
As rumored, the Camon 12 has a larger screen to body ratio, TECNO claims it is 90%, a good feat on its own but it also carries some bezels in form of a chin at the bottom and a waterdrop notch at the top.
- Advertisement -
Most of the phone is screen at the front while the back with its triple AI cameras and the fingerprint scanner is mostly plastic.
Actually, the design language doesn’t deviate so much from last years Camon 11 but its a different story when it comes to internals as you can see below, almost everything is an upgrade
- Display: 6.52-inch HD+ (~269 ppi density)
- Front Camera: 16 MP
- Rear Camera: 16 MP + 8 MP + 2 MP
- OS: Android 9.0 (Pie); HIOS 5.5
- Network: 4G/3G/2G
- Internal Storage: 64 GB
- RAM: 4 GB
- Processor: Mediatek MT6762 Helio P22 (12 nm)
- Battery: 4000mAh
It is also powered Android P atop TECNO’s own HiOS operating system. The phone at least performed well and never experienced hiccups but more remains to be told in our upcoming review.
Any Camon 12 complaints?
Apart from the intrusive ads over its HiOS skin, the TECNO Camon 12 seems to be a worthy upgrade from last year’s model but as we said, our review will determine whether you should consider it, shortlist or go ahead and buy it, but for how much?
Price and where to buy from
The Camon 12 retails between 600,000 and 700,000 UGX at TECNO branded shops countrywide.
- Advertisement -


Credit an order
note: you should only credit settlement trans_id or sale trans_id.
{% csrf_token %}
{{ form.as_ul }}


4
Welcome!
You are red.
Computer is yellow.
Start
v0.3
Reset
Wins!
Reset
Tie Game!
That was impressive!
Reset


KBS moves 'Battle Trip' time slot
by jennywill'Battle Trip' will be changing its time slot.
Currently, the KBS 2TV variety show airs on Saturday nights. However, starting on December 6th, the show will air instead at 8:30 PM KST on Friday nights. This is part of KBS's overhaul of their variety show timing as they revamp the new season of '1 Night 2 Days'.
Do you watch 'Battle Trip'?


Scalastyle examines your Scala code and indicates potential problems with it.
Place the config file in <root> or <root>/.idea or <root>/project directory.
Config file name can be scalastyle-config.xml or scalastyle_config.xml. Configs for test sources can be overridden by scalastyle-test-config.xml or scalastyle_test_config.xml.


WITNESS LIST
Education
November 13, 2020 10:00 AM
Best Practices
ON:
Conger, Eddie CEO/Superintendent (International Leadership of Texas), Richardson, TX
Kim, Andrew Superintendent of Schools (also providing written testimony) (Comal ISD),
New Braunfels, TX
McFarland, Michael Superintendent/CEO (also providing written testimony) (Crowley
ISD), Crowley, TX
Morath, Mike Commissioner of Education (also providing written testimony) (Texas
Education Agency), Austin, TX
Digital Learning
ON:
Bigenho, Chris Director of Virtual Learning Academy, Lewisville ISD (also providing
written testimony) (Lewisville Independent School District), Wylie, TX
Carey, Tiffany Director of Innovative Learning (also providing written testimony) (Firsco
Independent School District), Frisco, TX
Hinojosa, Michael Superintendent of Schools (Dallas Independent School District), Dallas,
TX
Martinez, Monica Associate Commissioner, Standards and Support Services (also
providing written testimony) (Texas Education Agency), Austin, TX
Monitoring - HB 3
ON:
Goffney, LaTonya Superintendent (Aldine ISD), Houston, TX
Lopez, Ricardo Superintendent (also providing written testimony) (Garland ISD), Garland,
TX
Morath, Mike Commissioner (also providing written testimony) (Texas Education
Agency), Austin, TX
Sanderson, Josh Deputy Executive Director (also providing written testimony) (Equity
Center), Austin, TX
Wilcox, James Superintendent (Longview ISD), Longview, TX
Monitoring - HB 3906
ON:
Brown, Kristin Superintendent (Lyford CISD), Lyford, TX
Gain, Kerry Chief Academic Officer (also providing written testimony) (Del Valle ISD),
New Braunfels, TX
Morath, Mike Commissioner of Education (also providing written testimony) (Texas
Education Agency), Austin, TX
Special Education
ON:
Aleman, Steve Senior Policy Specialist (also providing written testimony) (Disability
Rights Texas), Austin, TX
McGuire, Kristin Director of Governmental Relations (also providing written testimony)
(Texas Council of Administrators of Special Education), Austin, TX
Montano, Matt Deputy Commissioner for Special Populations and Monitoring (also
providing written testimony) (Texas Education Agency), Austin, TX


Scoreboard:
Player 1
Moves:
0
Score:
43
2
Moves:
0
Score:
72
Timer:
25:00
Intermediate level
Hard level
New game


|
tiff2bw − convert a color TIFF image to greyscale |
|
tiff2bw [ options ] input.tif output.tif |
|
Tiff2bw converts an RGB or Palette color TIFF image to a greyscale image by combining percentages of the red, green, and blue channels. By default, output samples are created by taking 28% of the red channel, 59% of the green channel, and 11% of the blue channel. To alter these percentages, the −R, −G, and −B options may be used. |
|
−c |
Specify a compression scheme to use when writing image data: −c none for no compression, −c packbits for the PackBits compression algorithm, −c zip for the Deflate compression algorithm, −c g3 for the CCITT Group 3 compression algorithm, −c g4 for the CCITT Group 4 compression algorithm, and −c lzw for Lempel-Ziv & Welch (the default). |
|||
|
−r |
Write data with a specified number of rows per strip; by default the number of rows/strip is selected so that each strip is approximately 8 kilobytes. |
|||
|
−R |
Specify the percentage of the red channel to use (default 28). |
|||
|
−G |
Specify the percentage of the green channel to use (default 59). |
|||
|
−B |
Specify the percentage of the blue channel to use (default 11). |
|
pal2rgb(1), tiffinfo(1), tiffcp(1), tiffmedian(1), libtiff(3) Libtiff library home page: http://www.remotesensing.org/libtiff/ |


Marine deserter sought in fatal shooting of Virginia man
by The Associated PressHARDY, Va. (AP) — Authorities in Virginia are searching for a Marine who deserted his post in North Carolina and has since been named as a person of interest in the shooting death of his mother’s boyfriend.
News outlets report 22-year-old Michael Alexander Brown is wanted for questioning in the Saturday death of Rodney Brown in Franklin County, Virginia. Authorities say Michael Brown is a combat engineer who deserted his Camp Lejeune post last month and was later spotted in Franklin County.
The Craven County Sheriff’s Office says Michael Brown may now be in North Carolina as his last known address was in New Bern, around 250 miles (400 kilometers) southeast of Franklin County and 50 miles (80 kilometers) north of Camp Lejeune.
Authorities say anyone who spots him should act with “extreme caution.”
The Associated Press


Across: 1. Exceptionally stingy land? (13) 10. Terrible text - decry tables all being borderless (9) 11. Most demand return for having title (5) 12. Cheating badly on run, makes split (15) 13. Lighting technician almost makes a blunder (5) 15. The claim's fantastic for this profession (9) 17. It's so bad, it has rest reeling (9) Most horrible as shitter exploded 18. Oil-laden vessel smashed ferry (5) 19. Try moving article - no strange USA schemes for these beasts (15) 22. Figures sent in backwards, with no total initially (5) 23. Suggest a tune is in order (9) 24. Remarks "master's senses addled!" (13) Down: 2. Manipulative types with cunning ruses (5) 3. I man "chess movers" - messy automated devices getting negative feedback (15) 4. Decide essential part: speed of data transfer (9) 5. Crazy armed fantasy (5) 6. Inner ten enforce peculiarly hands off approach (15) 7. Weak identity lacked specialist clarity (9) 8. Excessively drunken behaviour, perhaps, making a mess of giant tiger rug (13) 9. Brave women being broken - sure sad events (13) 14. Quality of some grapes in Champagne country? (9) Some agribusiness in France produces this 16. Eliminate girls with means to defend themselves (9) Attack women with knives or swords? 20. Box ideally holds rust (5) 21. Will halts game (5)


Whotowho
Get Version
0.2.0→ ‘How to assign one person to another’
What
WhoToWho is a simple script to find a random name in a list and send this name by email. With WhoToWho you can define several things:
- Mail subject
- Mail format with 2 parameters (who and towho)
- SMTP configuration
- SSL option if your SMTP connection is a Gmail account
History
WhoToWho was first made for my group of friends. For Christmas, we decided to group up and offer one present for one and only one other person. The present’s price is defined at the stort.
Before WhoToWho, we made a group before the Christmas eve and made the random choice of who must offer to who with a little paper in hat.
Now we live in different places and we can’t see each other every week. So, as we all have an email account, I created WhoToWho. This way nobody knows who must offer to who and the choice is totally random.
Yes, WhoToWho is useless. But what is really useful anyway?
Features
- Randomize a list and choose one person for another
- Send an email to each person to know to whom he or she is assigned
- Define the subject and the contents of the email with 2 parameters (who and to who)
- who and to who
- You may exclude one or several persons for another. With this feature, one person can’t have another defined person.
Installing
sudo gem install whotowho
Requirement
- ActionMailer from Ruby On Rails project :
gem install actionmailer
Example
ruby whotowho -m mail-example.txt -s 'a good subject' -f ../data.yaml -c conf.yaml -g
Example data file
This file defines the list of names and emails. The format of this file is YAML. It’s the serialization of an Array of an Array in Ruby. Here is an example of the format:
- - name - email - - name2 - email2 - - name3 - email3
If you want to exclude a person to another you need add an :exclude
list after the email, like the following:
- - name - email - :exclude: - name2 - - name2 - email2 - :exclude: - name5 - name6 - - name3 - email3 - - name4 - email4 - - name5 - email5 - - name6 - email6
Example mail configuration file
It’s the same as ActionMailer’s. But I have added the ability to add the “From” field. In order to do so you need to add :from:
:address: Allows you to use a remote mail server. Just change it from its default "localhost" setting. :port: On the off chance that your mail server doesn‘t run on port 25, you can change it. :domain: If you need to specify a HELO domain, you can do it here. :user_name: If your mail server requires authentication, set the username in this setting. :password: If your mail server requires authentication, set the password in this setting. :authentication: If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of :plain, :login, :cram_md5 :from: Define the email who send all mail in from header
Example of email content file
This file is a simple text file. You have two parameters which are changed before the email is sent:
- #{who} : the name of person who receives the email
- #{towho} : the name of the person assigned
How to submit patches
The trunk repository is http://whotowho.rubyforge.org/svn/trunk
for anonymous access. You can submit a patch by
RubyForge or by my own
development platform
Rdoc
You can consult the Rdoc of this project on the WhoToWho RDoc page
Thanks
Special Thanks to Sunny to his translation in good english of this page.
License
This code is free to use under the terms of the MIT license.
Contact
Comments are welcome. Send an email to Cyril Mougel
Cyril Mougel, 6th January 2008
Theme extended from Paul Battley


-
Revature Pizza
crafted from the best produce of New Jersey
veggie pizza
This is Big Pizza
And small Pizza


In Mak-Dental, we believe in the creation of exceptional smiles.
You can see our work here and the new smiles of our satisfied
patients.
Diastema mediana correction
Complete dentures construction
Partial dentures construction
Removal of tartar, pigments and stains
Complete dentures construction
Our dental office is designed by the highest standards, the office
space is completely adapted
and meets all the requerments from the Ministry of Health of the
Republic of North Macedonia.
Our clinic is actively working with patients from abroad. To make
their stay in our country more
pleasent, we offer help in the organisation of your trip, from
accommodation booking to
recommendations and offers for tours in
Bitola and the region.
Wide-alley, Bitola
Wide-alley, Bitola
Bitola square
Bitola square
Big Lake, Pelister, Mount Baba
Pelister-winter time
Pelister-spring time
Ski Centar Kopanki, Pelister


Walsall school holds 'parliament day'
The Conservative Parliamentary Candidate for Walsall North visited a school as part of its Parliament day.
by Jordan ReynoldsLeamore Primary School in Walsall held the event on Friday and Eddie Hughes came to meet the parents, students and staff and to join in with a quiz.
Antonia Thomas, school council coordinator, said: "We were celebrating UK Parliament Week and asked parents to come along and take part in a quiz and we asked Eddie Hughes to come along and he took part in the quiz with the parents and he presented the winning team with their prize.
"We had to have a tie-break question as two teams had the same scores.
"He had a look around the school and was introduced to the school council and to members of staff.
"He also had a chat with the parents and children.
"Parliament Week happens every year and last year we had the mayor and mayoress of Walsall for the day.
"The children and staff really enjoyed the visit. They all had fun and the quiz was educational too.
"It was nice for Eddie Hughes to come and visit and take part in our day."


{% import "molecules/social-media.html" as social_media with context %}
The drop-down list contains credit card issuers that have submitted credit card agreements as required under Section 204 of the Credit Card Accountability Responsibility and Disclosure Act of 2009 (the CARD Act). If you cannot find a card issuer, it may be that:
If none of these reasons apply and you still cannot find an agreement, call them to request a copy of your agreement. Under federal law, your credit card issuer is required to provide a copy of your agreement upon request.
Look on the back of the credit card or on your latest monthly statement to find the name of the issuer. The bank, retailer, or other entity whose name appears on the front of the card may not be the actual issuer of the card. You can also check the issuer's website; typically, the bank name is listed at the bottom of the issuer's homepage.
We will display the consumer credit card agreements in this database as the respective issuers submitted them. The CFPB is not responsible for the content of the agreements, including any discrepancies between an agreement as presented in this database and the agreement as offered to the public, or for any omissions or other errors in the agreement as submitted by the issuer.
The agreements on file will have general terms and conditions, pricing, and fee information. They are not specific to an individual's account information.
If you have questions about the agreements themselves, contact the card issuer directly.


Use the navigation buttons or the arrow keys to proceed to the remaining slides.
You can also press > >F < < on your keyboard to show your presentation in fullscreen mode.
This presentation is best explored with up-to-date web browsers.
Computer graphics is the creation or manipulation of static and dynamic images on the computer.
System Diagram
Input Devices
Processors - CPU, APU and GPU
Output Devices
Comes from a latin word 'rastrum' meaning rake. Electron draws the image every line forming a grid.
Directly manipulates the electron beam - forming an image.
OpenGL, short for "Open Graphics Library," is an application programming interface (API) designed for rendering 2D and 3D graphics. It provides a common set of commands that can be used to manage graphics in different applications and on multiple platforms.
Vulkan is a new generation graphics and compute API that provides high-efficiency, cross-platform access to modern GPUs used in a wide variety of devices from PCs and consoles to mobile phones and embedded platforms.
Microsoft DirectX is a collection of application programming interfaces for handling tasks related to multimedia, especially game programming and video, on Microsoft platforms.
WebGL enables web content to use an API based on OpenGL to perform 2D and 3D rendering in an HTML canvas in browsers that support it without the use of plug-ins.
Let us try OpenGL
import sys
import ctypes
import numpy as np
import OpenGL.GL as gl
import OpenGL.GLUT as glut
vertex_code = """
attribute vec2 position;
void main(){ gl_Position = vec4(position, 0.0, 1.0); } """
fragment_code = """
void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } """
def display():
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4)
glut.glutSwapBuffers()
def reshape(width,height):
gl.glViewport(0, 0, width, height)
def keyboard( key, x, y ):
if key == b'\x1b':
sys.exit( )
# GLUT init
# --------------------------------------
glut.glutInit()
glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGBA)
glut.glutCreateWindow('Hello world!')
glut.glutReshapeWindow(512,512)
glut.glutReshapeFunc(reshape)
glut.glutDisplayFunc(display)
glut.glutKeyboardFunc(keyboard)
# Build data
# --------------------------------------
data = np.zeros(4, [("position", np.float32, 2)])
data['position'] = [(-1,+1), (+1,+1), (-1,-1), (+1,-1)]
# Build & activate program
# --------------------------------------
# Request a program and shader slots from GPU
program = gl.glCreateProgram()
vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
# Set shaders source
gl.glShaderSource(vertex, vertex_code)
gl.glShaderSource(fragment, fragment_code)
# Compile shaders
gl.glCompileShader(vertex)
if not gl.glGetShaderiv(vertex, gl.GL_COMPILE_STATUS):
error = gl.glGetShaderInfoLog(vertex).decode()
print(error)
raise RuntimeError("Shader compilation error")
gl.glCompileShader(fragment)
gl.glCompileShader(fragment)
if not gl.glGetShaderiv(fragment, gl.GL_COMPILE_STATUS):
error = gl.glGetShaderInfoLog(fragment).decode()
print(error)
raise RuntimeError("Shader compilation error")
# Attach shader objects to the program
gl.glAttachShader(program, vertex)
gl.glAttachShader(program, fragment)
# Build program
gl.glLinkProgram(program)
if not gl.glGetProgramiv(program, gl.GL_LINK_STATUS):
print(gl.glGetProgramInfoLog(program))
raise RuntimeError('Linking error')
# Get rid of shaders (no more needed)
gl.glDetachShader(program, vertex)
gl.glDetachShader(program, fragment)
# Make program the default program
gl.glUseProgram(program)
# Build buffer
# --------------------------------------
# Request a buffer slot from GPU
buffer = gl.glGenBuffers(1)
# Make this buffer the default one
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
# Upload data
gl.glBufferData(gl.GL_ARRAY_BUFFER, data.nbytes, data, gl.GL_DYNAMIC_DRAW)
# Bind the position attribute
# --------------------------------------
stride = data.strides[0]
offset = ctypes.c_void_p(0)
loc = gl.glGetAttribLocation(program, "position")
gl.glEnableVertexAttribArray(loc)
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buffer)
gl.glVertexAttribPointer(loc, 2, gl.GL_FLOAT, False, stride, offset)
# Enter the mainloop
# --------------------------------------
glut.glutMainLoop()
"Patience is now a skill"
Using glumpy
from glumpy import app, gloo, gl
vertex = """
attribute vec2 position;
void main(){ gl_Position = vec4(position, 0.0, 1.0); } """
fragment = """
void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } """
# Create a window with a valid GL context
window = app.Window()
# Build the program and corresponding buffers (with 4 vertices)
quad = gloo.Program(vertex, fragment, count=4)
# Upload data into GPU
quad['position'] = (-1,+1), (+1,+1), (-1,-1), (+1,-1)
# Tell glumpy what needs to be done at each redraw
@window.event
def on_draw(dt):
window.clear()
quad.draw(gl.GL_TRIANGLE_STRIP)
# Run the app
app.run()
How's Graphics Programming with OpenGL?


Associated words
Search
And
Or
words proximity
sources
classifierSource
custom
BNF
DBPEDIA
GoogleNLP
unesco
PLM
login
password
OK


Hey, how was your work week?
This is entirely optional. Your answers will be directly visible to only your supervisor, and also will be aggregated into a department-wide average. If you'd like to see the aggregated results so far without actually submitting a rating, click here. Love, Feelings Bot |


best websites of the world
English
DONATE FOR A CAUSE
HELP THE NEEDY
BE THE CHANGE
BHawna
BHAWNA
bhawna
bhawna
bhawna
bhawna
bhawna
bhawna.
Latest Projects
bhawna
bhawna
bhawna
bhawna
bhawna
bhawna
bhawna
bhawna
Contact Us
Hm, somethingís not right...

Name
E-mail
URL
Budget
$500 - $1000
$500 - $1000
$1000 - $2500
$2500 - $5000
$5000 - $7500
$7500 - $10000
Interested in
Adapt my website to smartphones
Adapt my website to smartphones and tablets
Partial redesign + adapt my website to smartphones and tablets
Complete responsive design makeover of my current website
A new website with responsive design might be a good idea, letís talk.
Details


See below for our Feminist Spotlight, a calendar of upcoming events, and a twitter stream of those joining in on the #MeToo movement.


So here's something I threw together just to see how easy it would be to make.  Grr. Oh and I know that the grass is all the same. Work in progress
I'm going to make and test maps this weekend to get the creation process as streamlined as I can before releasing this thing to you guys! A couple of the functions are...well, lets say, only the programmer can interpret what they mean.
Edit: man that waterfall looks a lot cooler when its moving...oh well