Browse Source

improved waiting for elements: defaults to timeout-1 if element should be there, defaults to timeout/3 if element is optional

tags/201811-before-redesign
Marc Wäckerlin 3 years ago
parent
commit
605c4eea79
8 changed files with 356 additions and 43 deletions
  1. +1
    -1
      COPYING
  2. +1
    -1
      INSTALL
  3. +3
    -3
      ax_check_qt.m4
  4. +2
    -1
      ax_init_standard_project.m4
  5. +20
    -19
      bootstrap.sh
  6. +171
    -0
      dependency-graph.sh
  7. +37
    -18
      src/commands.hxx
  8. +121
    -0
      template.sh

+ 1
- 1
COPYING View File

@@ -1 +1 @@
/usr/share/automake-1.13/COPYING
/usr/share/automake-1.15/COPYING

+ 1
- 1
INSTALL View File

@@ -1 +1 @@
/usr/share/automake-1.13/INSTALL
/usr/share/automake-1.15/INSTALL

+ 3
- 3
ax_check_qt.m4 View File

@@ -274,9 +274,9 @@ AC_DEFUN([AX_QT_NO_KEYWORDS], [
])

AC_DEFUN([AX_INIT_QT], [
test -f src/makefile.in && cat >> src/makefile.in <<EOF
#### HERE
if test -n "${AX_ADDITIONAL_QT_RULES_HACK}"; then
test -f src/makefile.in && cat >> src/makefile.in <<EOF
${AX_ADDITIONAL_QT_RULES_HACK}
#### WE ARE
EOF
fi
])

+ 2
- 1
ax_init_standard_project.m4 View File

@@ -90,7 +90,7 @@ dnl refers to ${prefix}. Thus we have to use `eval' twice.
# $3 = filename of makefile.in
AC_DEFUN([AX_ADD_MAKEFILE_TARGET_DEP], [
sh_add_makefile_target_dep() {
sed -i -e ':a;/^'${1}':.*\\$/{N;s/\\\n//;ta};s/^'${1}':.*$/& '${2}'/' "${srcdir}/${3}"
sed -i -e ':a;/^'${1}':.*\\$/{N;s/\\\n//;ta};s/^'"${1}"':.*$/& '"${2}"'/' "${srcdir}/${3}"
if ! egrep -q "${1}:.* ${2}" "${srcdir}/${3}"; then
echo "${1}: ${2}" >> "${srcdir}/${3}"
fi
@@ -1229,4 +1229,5 @@ AC_DEFUN([AX_OUTPUT], [
AX_RPM_RESOLVE
AC_OUTPUT
AX_INIT_QT
AC_MSG_NOTICE([configured for ${PACKAGE_NAME}-${VERSION}])
])

+ 20
- 19
bootstrap.sh View File

@@ -138,6 +138,8 @@ GENERATED FILES
* build-resource-file.sh - build resource.qrc file from a resource directory
* sql-to-dot.sed - script to convert SQL schema files to graphviz dot in doxygen
* mac-create-app-bundle.sh - script to create apple mac os-x app-bundle
* dependency-graph.sh - script to draw project dependencies
* template.sh - generic template for bash scripts
* test/runtests.sh - template file to run test scripts, i.e. docker based
* AUTHORS - replace your name in AUTHORS before first run
* NEWS - empty file add your project's news
@@ -549,6 +551,8 @@ copy rpmsign.exp
copy build-resource-file.sh
copy sql-to-dot.sed
copy mac-create-app-bundle.sh
copy dependency-graph.sh
copy template.sh
AUTHOR=$(gpg -K 2>/dev/null | sed -n 's,uid *\(\[ultimate\] *\)\?,,p' | head -1)
if test -z "${AUTHOR}"; then
AUTHOR="FIRSTNAME LASTNAME (URL) <EMAIL>"
@@ -1635,7 +1639,7 @@ BuildRequires: which, pkgconfig, gnupg, expect, ${VCSDEPENDS_RPM}make, automake,
%if 0%{?suse_version} || 0%{?sles_version}
BuildRequires: lsb-release$(
if testtag AX_REQUIRE_QT || testtag AX_CHECK_QT; then
echo -n ", libqt5-qtbase-devel, libqt5-qttools, libQt5WebKit5-devel libqt5-qtwebengine-devel libQt5WebKitWidgets-devel";
echo -n ", libqt5-qtbase-devel, libqt5-qttools, libqt5-linguist-devel, libQt5WebKit5-devel libqt5-qtwebengine-devel libQt5WebKitWidgets-devel";
fi)
%else
$(
@@ -1695,25 +1699,20 @@ rm -rf \$RPM_BUILD_ROOT
$(if testtag AX_USE_LIBTOOL; then
echo '/usr/%_lib/*.so.*'
else
echo '/usr/bin/*'
echo '/usr/share/applications/*'
echo '/usr/bin'
echo '/usr/share/applications'
fi)
$(if testtag AX_USE_NODEJS AX_BUILD_HTML AX_BUILD_HTML_NPM; then
echo '/usr/share/@PACKAGE_NAME@'
fi)
%doc
$(if testtag AX_USE_LIBTOOL; then
cat <<EOF2
/usr/share/doc/packages/@PACKAGE_NAME@/AUTHORS
/usr/share/doc/packages/@PACKAGE_NAME@/COPYING
/usr/share/doc/packages/@PACKAGE_NAME@/ChangeLog
/usr/share/doc/packages/@PACKAGE_NAME@/INSTALL
/usr/share/doc/packages/@PACKAGE_NAME@/NEWS
/usr/share/doc/packages/@PACKAGE_NAME@/README
/usr/share/@PACKAGE_NAME@
$(if testtag AX_USE_ETC; then

cat <<EOF2
%config
/etc/*

EOF2
else
echo '/usr/share'
fi)
%doc
/usr/share/doc

$(if testtag AX_USE_LIBTOOL; then
cat <<EOF2
@@ -1792,12 +1791,14 @@ ${HEADER}SUBDIRS =${SUBDIRS}

desktopdir = \${datadir}/applications
desktop_DATA = @PACKAGE_DESKTOP@
dist_pkgdata_DATA = @PACKAGE_ICON@ ax_check_qt.m4 bootstrap.sh \\
resolve-rpmbuilddeps.sh autogen.sh \\
dist_pkgdata_DATA = @PACKAGE_ICON@
dist_noinst_DATA = ax_check_qt.m4 bootstrap.sh \\
resolve-rpmbuilddeps.sh autogen.sh \\
ax_cxx_compile_stdcxx_11.m4 build-in-docker.sh \\
build-resource-file.sh \\
ax_init_standard_project.m4 \\
mac-create-app-bundle.sh resolve-debbuilddeps.sh \\
dependency-graph.sh template.sh \\
sql-to-dot.sed
dist_doc_DATA = AUTHORS NEWS README COPYING INSTALL ChangeLog



+ 171
- 0
dependency-graph.sh View File

@@ -0,0 +1,171 @@
#!/bin/bash -e

# template for bash scripts

# internal use only
append_msg() {
if test $# -ne 0; then
echo -en ":\e[0m \e[1m$*"
fi
echo -e "\e[0m"
}

# write a notice
notice() {
if test $# -eq 0; then
return
fi
echo -e "\e[1m$*\e[0m" 1>&3
}

# write error message
error() {
echo -en "\e[1;31merror" 1>&2
append_msg $* 1>&2
}

# write a warning message
warning() {
echo -en "\e[1;33mwarning" 1>&2
append_msg $* 1>&2
}

# write a success message
success() {
echo -en "\e[1;32msuccess" 1>&2
append_msg $* 1>&2
}

# commandline parameter evaluation
files=${0%/*}/configure.ac
short=0
while test $# -gt 0; do
case "$1" in
(--short|-s) short=1;;
(--help|-h) less <<EOF
SYNOPSIS

$0 [OPTIONS] <files>

OPTIONS

--help, -h show this help
--short, -s short graph with no external dependencies

<files> list of zero or more configure.ac files
(default: ${files})

DESCRIPTION

Evaluates dependencies of all the given configure.ac file. By
default takes the local configure.ac. Outputs a graphwiz dot file
with the dependencies. Solid lines are required dependencies, dotted
lines are optional dependencies.

EXAMPLE

Evaluate all dependencies between all local subversion and git
projects, if they are in the path ~/svn and ~/git:

$0 ~/svn/*/configure.ac ~/git/*/configure.ac

EOF
exit;;
(*) files=$*; break;;
esac
if test $# -eq 0; then
error "missing parameter, try $0 --help"; exit 1
fi
shift;
done

# run a command, print the result and abort in case of error
# option: --no-check: ignore the result, continue in case of error
run() {
check=1
while test $# -gt 0; do
case "$1" in
(--no-check) check=0;;
(*) break;;
esac
shift;
done
echo -en "\e[1m-> running:\e[0m $* ..."
result=$($* 2>&1)
res=$?
if test $res -ne 0; then
if test $check -eq 1; then
error "failed with return code: $res"
if test -n "$result"; then
echo "$result"
fi
exit 1
else
warning "ignored return code: $res"
fi
else
success
fi
}

# error handler
function traperror() {
set +x
local err=($1) # error status
local line="$2" # LINENO
local linecallfunc="$3"
local command="$4"
local funcstack="$5"
for e in ${err[@]}; do
if test -n "$e" -a "$e" != "0"; then
error "line $line - command '$command' exited with status: $e (${err[@]})"
if [ "${funcstack}" != "main" -o "$linecallfunc" != "0" ]; then
echo -n " ... error at ${funcstack} "
if [ "$linecallfunc" != "" ]; then
echo -n "called at line $linecallfunc"
fi
echo
fi
exit $e
fi
done
success
exit 0
}

# catch errors
trap 'traperror "$? ${PIPESTATUS[@]}" $LINENO $BASH_LINENO "$BASH_COMMAND" "${FUNCNAME[@]}" "${FUNCTION}"' ERR SIGINT INT TERM EXIT

##########################################################################################

filter() {
if test $short -eq 1; then
all=$(cat)
allowed=$(sed -n '/"\(.*\)" \[style=solid\];/{s//\1/;H};${x;s/\n//;s/\n/\\|/gp}' <<<"${all}")
sed -n '/"\('"${allowed}"'\)" -> "\('"${allowed}"'\)"/p' <<<"${all}"
else
cat
fi
}

echo "digraph G {"
if test $short -eq 0; then
echo "node [style=dashed];"
fi
(
for file in $files; do
if ! test -e $file; then
error "file $file not found"; exit 1
fi
sed -n '
/^ *m4_define(x_package_name, */ {s//"/;s/ *).*/"/;h;s/.*/& [style=solid];/p}
/^ *AX_REQUIRE_QT/ {s/.*/"qt" -> /;G;s/\n//;s/.*/&;/p}
/^ *AX_PKG_REQUIRE(\[\?\([^],)]\+\)\]\?, \[\?\([^],)]\+\)\]\?.*/ {s//"\2" -> /;G;s/\n//;s/.*/&;/p}
/^ *AX_PKG_REQUIRE(\[\?\([^],)]\+\)\]\?.*/ {s//"\1" -> /;G;s/\n//;s/.*/&;/p}
/^ *AX_CHECK_QT/ {s/.*/"qt" -> /;G;s/\n//;s/.*/& [style=dashed];/p}
/^ *AX_PKG_CHECK(\[\?\([^],)]\+\)\]\?, \[\?\([^],)]\+\)\]\?.*/ {s//"\2" -> /;G;s/\n//;s/.*/& [style=dotted];/p}
/^ *AX_PKG_CHECK(\[\?\([^],)]\+\)\]\?.*/ {s//"\1" -> /;G;s/\n//;s/.*/& [style=dotted];/p}
' $file
done
) | filter
echo "}"

+ 37
- 18
src/commands.hxx View File

@@ -33,6 +33,7 @@
#include <sstream>
#include <cassert>
#include <xml-cxx/xml.hxx>
#include <mrw/stdext.hxx>

#ifdef HAVE_CXXABI_H
#include <cxxabi.h>
@@ -151,11 +152,8 @@ class Command: public QObject {
virtual bool isTestcase() {
return true;
}
static void realMouseClick(Logger& log, QWebFrame* frame, QString selector) {
QWebElement element(find(frame, selector));
if (element.isNull()) error(log, ElementNotFound(selector));
realMouseClick(element);
}
static void realMouseClick(Logger& log, QWebFrame* frame,
Script* script, QString selector);
static void realMouseClick(const QWebElement& element) {
QWidget* web(element.webFrame()->page()->view());
QRect elGeom=element.geometry();
@@ -248,8 +246,9 @@ class Command: public QObject {
return element;
}
static QWebElement find1(QWebFrame* frame, QString selector,
int repeat = 5, int sleepsec = 1) {
int repeat = 2, int sleepsec = 1) {
QWebElement element;
if (repeat<1) repeat=1;
for (int i=0; i<repeat; ++i) {
element = frame->findFirstElement(selector);
if (!element.isNull()) return element;
@@ -257,7 +256,7 @@ class Command: public QObject {
element = find1(childFrame, selector, 1, 0);
if (!element.isNull()) return element;
}
if (sleepsec) sleep(sleepsec);
if (i+1<repeat && sleepsec) sleep(sleepsec);
}
return element;
}
@@ -855,7 +854,8 @@ class Script: public QObject {
void ignore(const Script& o) {
_ignore = o._ignore;
}
bool ignore(const QString& sig) {
bool ignores(const QString& sig = QString()) {
if (sig.isEmpty()) return !_ignore.empty();
return _ignore.contains(sig);
}
void ignore(QStringList sigs = QStringList()) {
@@ -932,6 +932,9 @@ class Script: public QObject {
void timeout(int t) {
_timeout = t;
}
int timeout() {
return _timeout;
}
void defaultTimeout(int t) {
_defaultTimeout = t;
}
@@ -995,7 +998,7 @@ class Script: public QObject {
SLOT(titleChanged(const QString&))));
assert(connect(frame, SIGNAL(urlChanged(const QUrl&)),
SLOT(urlChanged(const QUrl&))));
assert(connect(&_timer, SIGNAL(timeout()), SLOT(timeout())));
assert(connect(&_timer, SIGNAL(timeout()), SLOT(timedout())));
}
void removeSignals(QWebFrame* frame) {
disconnect(dynamic_cast<NetworkAccessManager*>
@@ -1023,7 +1026,7 @@ class Script: public QObject {
this, SLOT(urlChanged(const QUrl&)));
disconnect(frame, SIGNAL(urlChanged(const QUrl&)),
this, SLOT(urlChanged(const QUrl&)));
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(timeout()));
disconnect(&_timer, SIGNAL(timeout()), this, SLOT(timedout()));
}
public Q_SLOTS:
void log(QString text, Command* command = 0) {
@@ -1144,7 +1147,7 @@ class Script: public QObject {
_signals.push(std::make_pair("urlChanged",
QStringList(url.toString())));
}
void timeout() {
void timedout() {
error(TimeOut());
}
private:
@@ -1222,7 +1225,7 @@ class Do: public Command {
Logger log(this, script);
QWebElement element(frame->documentElement());
if (_selector.size()) {
element = find(frame, script->replacevars(_selector));
element = find(frame, script->replacevars(_selector), script->timeout()-1);
if (element.isNull())
error(log, ElementNotFound(script->replacevars(_selector)));
}
@@ -1305,7 +1308,7 @@ class Expect: public Command {
Logger log(this, script);
QStringList args(script->replacevars(_signal._args));
if (_signal._signal=="load") { // special signal load
while (!script->ignore("loadStarted") && script->checkSignal("loadStarted")) {
while (!script->ignores("loadStarted") && script->checkSignal("loadStarted")) {
log("ignore signal: loadStarted"); // ignore optional loadStarted
}
if (!script->checkSignal("urlChanged", args))
@@ -1532,7 +1535,7 @@ class Upload: public Command {
if (files.size()==1) filename=files[0];
}
page->setNextUploadFile(filename);
realMouseClick(log, frame, script->replacevars(_selector));
realMouseClick(log, frame, script, script->replacevars(_selector));
if (page->uploadPrepared())
error(log, SetFileUploadFailed(script->replacevars(_selector), filename));
return true;
@@ -1576,7 +1579,7 @@ class Exists: public Command {
QString selector(script->replacevars(_selector));
QString text(script->replacevars(_text));
QStringList notfound;
QWebElement firstelement(find(frame, selector));
QWebElement firstelement(find(frame, selector, script->timeout()-1));
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
if (text.isEmpty()) return true; // just find element
if (element.toOuterXml().indexOf(text)!=-1) return true;
@@ -1631,6 +1634,8 @@ class Not: public Command {
Logger log(this, script);
QString selector(script->replacevars(_selector));
QString text(script->replacevars(_text));
QWebElement firstelement(find(frame, selector,
mrw::max(mrw::min(script->timeout()/3, 10), 2)));
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
if (text.isEmpty())
error(log, AssertionFailed("element must not exists: "+selector));
@@ -1828,12 +1833,12 @@ class Click: public Command {
QString clicktarget(script->replacevars(_selector));
switch (_clicktype ? *_clicktype : script->clicktype()) {
case Script::REAL_MOUSE_CLICK: {
realMouseClick(log, frame, clicktarget);
realMouseClick(log, frame, script, clicktarget);
break;
}
case Script::JAVASCRIPT_CLICK:
default: {
QWebElement element(find(frame, clicktarget));
QWebElement element(find(frame, clicktarget, script->timeout()-1));
if (element.isNull())
error(log, ElementNotFound(clicktarget));
_result = element.evaluateJavaScript("this.click();").toString();
@@ -2145,7 +2150,8 @@ class SetValue: public Command {
}
bool execute(Script* script, QWebFrame* frame) {
Logger log(this, script);
QWebElement element(find(frame, script->replacevars(_selector)));
QWebElement element(find(frame, script->replacevars(_selector),
script->timeout()-1));
if (element.isNull())
error(log, ElementNotFound(script->replacevars(_selector)));
QString value(script->replacevars(_value));
@@ -2334,6 +2340,8 @@ class If: public CommandContainer {
QString selector(script->replacevars(_variable));
bool check(false);
if (_cmp=="->") {
QWebElement firstelement(find(frame, selector,
mrw::max(mrw::min(script->timeout()/3, 10), 2)));
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
if (value.isEmpty() || // just find element
element.toOuterXml().indexOf(value)!=-1 ||
@@ -2439,6 +2447,8 @@ class While: public CommandContainer {
for (bool check(true); check;) {
if (_cmp=="->") {
check = false;
QWebElement firstelement(find(frame, selector,
mrw::max(mrw::min(script->timeout()/3, 10), 2)));
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
if (value.isEmpty() || // just find element
element.toOuterXml().indexOf(value)!=-1 ||
@@ -2899,6 +2909,8 @@ class Case: public Command {
log("terminate with default branch");
check = true;
} else if (condition.cmp=="->") {
QWebElement firstelement(find(frame, selector,
mrw::max(mrw::min(script->timeout()/3, 10), 2)));
Q_FOREACH(QWebElement element, frame->findAllElements(selector)) {
if (value.isEmpty() || // just find element
element.toOuterXml().indexOf(value)!=-1 ||
@@ -3159,6 +3171,13 @@ inline Logger::~Logger() {
}
}

inline void Command::realMouseClick(Logger& log, QWebFrame* frame,
Script* script, QString selector) {
QWebElement element(find(frame, selector, script->timeout()-1));
if (element.isNull()) error(log, ElementNotFound(selector));
realMouseClick(element);
}

inline std::shared_ptr<Script> Command::subParser(Script* parent, const QStringList& in,
const QString& file,
int line, int indent) {


+ 121
- 0
template.sh View File

@@ -0,0 +1,121 @@
#!/bin/bash -e

# template for bash scripts

# internal use only
append_msg() {
if test $# -ne 0; then
echo -en ":\e[0m \e[1m$*"
fi
echo -e "\e[0m"
}

# write a notice
notice() {
if test $# -eq 0; then
return
fi
echo -e "\e[1m$*\e[0m" 1>&3
}

# write error message
error() {
echo -en "\e[1;31merror" 1>&2
append_msg $* 1>&2
}

# write a warning message
warning() {
echo -en "\e[1;33mwarning" 1>&2
append_msg $* 1>&2
}

# write a success message
success() {
echo -en "\e[1;32msuccess" 1>&2
append_msg $* 1>&2
}

# commandline parameter evaluation
while test $# -gt 0; do
case "$1" in
(--help|-h) less <<EOF
SYNOPSIS

$0 [OPTIONS]

OPTIONS

--help, -h show this help

DESCRIPTION

EOF
exit;;
(*) error "unknow option $1, try $0 --help"; exit 1;;
esac
if test $# -eq 0; then
error "missing parameter, try $0 --help"; exit 1
fi
shift;
done

# run a command, print the result and abort in case of error
# option: --no-check: ignore the result, continue in case of error
run() {
check=1
while test $# -gt 0; do
case "$1" in
(--no-check) check=0;;
(*) break;;
esac
shift;
done
echo -en "\e[1m-> running:\e[0m $* ..."
result=$($* 2>&1)
res=$?
if test $res -ne 0; then
if test $check -eq 1; then
error "failed with return code: $res"
if test -n "$result"; then
echo "$result"
fi
exit 1
else
warning "ignored return code: $res"
fi
else
success
fi
}

# error handler
function traperror() {
set +x
local err=($1) # error status
local line="$2" # LINENO
local linecallfunc="$3"
local command="$4"
local funcstack="$5"
for e in ${err[@]}; do
if test -n "$e" -a "$e" != "0"; then
error "line $line - command '$command' exited with status: $e (${err[@]})"
if [ "${funcstack}" != "main" -o "$linecallfunc" != "0" ]; then
echo -n " ... error at ${funcstack} "
if [ "$linecallfunc" != "" ]; then
echo -n "called at line $linecallfunc"
fi
echo
fi
exit $e
fi
done
success
exit 0
}

# catch errors
trap 'traperror "$? ${PIPESTATUS[@]}" $LINENO $BASH_LINENO "$BASH_COMMAND" "${FUNCNAME[@]}" "${FUNCTION}"' ERR SIGINT INT TERM EXIT

##########################################################################################


Loading…
Cancel
Save