Showing preview only (350K chars total). Download the full file or copy to clipboard to get everything.
Repository: Chras-fu/Liuma-engine
Branch: main
Commit: 5bae0a8fab43
Files: 52
Total size: 315.5 KB
Directory structure:
gitextract_htwkq7hi/
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── browser/
│ └── readme.md
├── config/
│ └── config.ini
├── core/
│ ├── api/
│ │ ├── collector.py
│ │ ├── testcase.py
│ │ └── teststep.py
│ ├── app/
│ │ ├── collector.py
│ │ ├── device/
│ │ │ ├── __init__.py
│ │ │ ├── assertionOpt.py
│ │ │ ├── conditionOpt.py
│ │ │ ├── relationOpt.py
│ │ │ ├── scenarioOpt.py
│ │ │ ├── systemOpt.py
│ │ │ └── viewOpt.py
│ │ ├── find_opt.py
│ │ ├── testcase.py
│ │ └── teststep.py
│ ├── assertion.py
│ ├── template.py
│ └── web/
│ ├── collector.py
│ ├── driver/
│ │ ├── __init__.py
│ │ ├── assertionOpt.py
│ │ ├── browserOpt.py
│ │ ├── conditionOpt.py
│ │ ├── pageOpt.py
│ │ ├── relationOpt.py
│ │ └── scenarioOpt.py
│ ├── find_opt.py
│ ├── testcase.py
│ └── teststep.py
├── lm/
│ ├── lm_api.py
│ ├── lm_case.py
│ ├── lm_config.py
│ ├── lm_log.py
│ ├── lm_report.py
│ ├── lm_result.py
│ ├── lm_run.py
│ ├── lm_setting.py
│ ├── lm_start.py
│ ├── lm_upload.py
│ └── lm_ws.py
├── requirements.txt
├── startup.py
└── tools/
├── funclib/
│ ├── __init__.py
│ ├── load_faker.py
│ ├── params_enum.py
│ └── provider/
│ └── lm_provider.py
└── utils/
├── sql.py
└── utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
**/__pycache__/
.idea
image
log
data
file
browser/**.exe
================================================
FILE: Dockerfile
================================================
FROM python:3.8
MAINTAINER "liuma"
COPY browser /liuma/browser
COPY core /liuma/core
COPY requirements.txt /liuma/
COPY tools/ /liuma/tools
COPY lm/ /liuma/lm
COPY startup.py /liuma/
WORKDIR /liuma
RUN pip install -r requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
CMD ["python", "startup.py"]
================================================
FILE: LICENSE
================================================
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (c) 2022 The Liuma Project of Chras-fu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
================================================
FILE: README.md
================================================
# 流马-低代码测试平台
## 一、项目概述
流马是一款低代码自动化测试平台,旨在采用最简单的架构统一支持API/WebUI/AppUI的自动化测试。平台采用低代码设计模式,将传统测试脚本以配置化实现,从而让代码能力稍弱的用户快速上手自动化测试。同时平台也支持通过简单的代码编写实现自定义组件,使用户可以灵活实现自己的需求。
本项目分为平台端和引擎端,采用分布式执行设计,可以将测试执行的节点(即引擎)注册在任意环境的任意一台机器上,从而突破资源及网络限制。同时,通过将引擎启动在本地PC上,方便用户快速调试测试用例,实时查看执行过程,带来传统脚本编写一致的便捷。
在线体验: [演示平台](http://demo-ee.liumatest.cn)
官网地址: [流马官网](http://www.liumatest.cn)
社区地址: [流马社区](http://community.liumatest.cn)
配套开发教程: [B站课堂](https://www.bilibili.com/cheese/play/ss7009)
如果本项目对您有帮助,请给我们一个Star,您的支持是我们前进的动力。
如果您需要二次开发,请务必遵循AGPL开源协议,并保留版权信息。我们保留一切对于侵权行为追责的权利。
## 二、功能介绍

1. API测试
```
(1) 支持单接口测试和链路测试。
(2) 支持接口统一管理,支持postman/swagger导入。
(3) 支持一键生成字段校验的接口健壮性用例。
(4) 支持全局变量、关联、断言、内置函数、自定义函数。
(5) 支持前后置脚本、失败继续、超时时间、等待/条件/循环等逻辑控制器。
(6) 支持环境与用例解耦,多种方式匹配域名,让一套用例可以在多个环境上执行。
```
2. WebUI测试
```
(1) 支持关键字驱动,零代码编写用例。
(2) 支持UI元素统一管理,Excel模板批量导入。
(3) 支持自定义关键字,封装公共的操作步骤,提升用例可读性。
(4) 支持本地引擎执行,实时查看执行过程。
(5) 支持与API用例在同一用例集合顺序执行。
```
3. AppUI测试
```
(1) 支持WebUI同等用例编写和执行能力
(2) 支持安卓和苹果系统
(3) 支持持真机管理、投屏和在线操作
(4) 支持控件元素在线获取,一键保存元素
(5) 支持实时查看执行过程
```
更多功能及详细请参考: [用户手册](http://www.liumatest.cn/productDoc)
## 三、开发环境
环境依赖: Python3.8、Chrome、ChromeDriver(参考:[驱动说明](./browser/readme.md))
IDE推荐: python使用pyCharm
1. 引擎启动
```
Step1: 安装依赖包 pip3 install -r requirements.txt
Step2: 流马测试平台->引擎管理->注册引擎 保存engine-code和engine-secret
Step3: engine-code和engine-secret填写在/config/config.ini文件中对应位置
Step4: 修改/config/config.ini文件中Platform->url为后端地址
Step5: 如linux启动,修改/config/config.ini文件中WebDriver->options为headless
Step6: 如linux/mac启动,修改/config/config.ini文件中WebDriver->path为chromedriver
Step7: 启动引擎 python3 startup.py
```
2. 验证启动
平台引擎管理查看自己的引擎,显示在线,证明启动成功。再编写一个简单的接口用例并执行,执行成功并返回报告,引擎注册完成。
## 四、容器部署
容器部署请参考: [部署手册](http://www.liumatest.cn/deployDoc)
## 五、关于我们
流马秉持着帮助中小企业的测试团队快速建立自动化体系的目标,将会不断迭代并吸取用户的建议,欢迎大家给我们提出宝贵的意见。
如需学习平台开发相关内容或在线交流,可关注个人微信公众号【流马测试】

================================================
FILE: browser/readme.md
================================================
### Chromedriver
selenium驱动谷歌浏览器依赖于chromedriver, 因此启动引擎前需要下载驱动。
+ 下载地址
推荐使用淘宝下载源[下载链接](http://npm.taobao.org/mirrors/chromedriver/)
+ 使用说明
chromedriver版本需要与引擎所在机器安装的chrome浏览器相对应,且浏览器尽量不要使用最新版本。
将chromedriver下载后,复制到当前目录下即可。
================================================
FILE: config/config.ini
================================================
[Platform]
url = http://127.0.0.1:8080
enable-stderr = true
[Engine]
engine-code = ******
engine-secret = ******
[Header]
content-type = application/json;charset=utf-8
token = ******
[WebDriver]
options = normal
path = chromedriver.exe
[RunSetting]
max-run = 2
================================================
FILE: core/api/collector.py
================================================
import json
import re
from tools.utils.utils import proxies_join, handle_form_data, handle_files
class ApiRequestCollector:
def __init__(self):
self.apiId = None
self.apiName = None
self.method = None
self.url = None
self.path = None
self.protocol = None
self.body_type = None
self.others = {}
self.controller = {}
self.looper = {}
self.conditions = []
self.assertions = []
self.relations = []
def collect_flag(self, api_data, arg_name):
if arg_name not in api_data or api_data[arg_name] is None:
raise NotExistedFieldError('接口数据{}字段不存在或为空'.format(arg_name))
elif type(api_data[arg_name]) is str and len(api_data[arg_name]) == 0:
raise NotExistedFieldError('接口数据{}字段长度为0'.format(arg_name))
else:
setattr(self, arg_name, api_data[arg_name])
def collect_other(self, api_data, arg_name, func=lambda x: x):
if arg_name not in api_data or api_data[arg_name] is None or len(api_data[arg_name]) == 0:
self.others[arg_name] = None
else:
self.others[arg_name] = func(api_data[arg_name])
def collect_context(self, api_data, arg_name):
if arg_name not in api_data or api_data[arg_name] is None or len(api_data[arg_name]) == 0:
setattr(self, arg_name, None)
else:
setattr(self, arg_name, api_data[arg_name])
def collect_id(self, api_data):
self.collect_flag(api_data, "apiId")
def collect_name(self, api_data):
self.collect_flag(api_data, "apiName")
def collect_protocol(self, api_data):
self.collect_flag(api_data, "protocol")
def collect_method(self, api_data):
if 'method' not in api_data or api_data['method'] is None or len(api_data['method']) == 0:
raise UnDefinableMethodError("接口{}未定义请求方法".format(api_data['apiId']))
method = api_data['method'].upper()
self.method = method
def collect_url(self, api_data):
if 'url' not in api_data:
raise UnDefinablePathError("接口{}未设置域名".format(api_data['apiId']))
else:
self.url = api_data['url']
def collect_path(self, api_data):
if 'path' not in api_data:
raise UnDefinablePathError("接口{}未设置路径".format(api_data['apiId']))
else:
fields = re.findall(r'\{(.*?)\}', api_data['path'])
path = api_data['path']
for field in fields:
result = "{%s}" % field
if field in api_data['rest']:
result = api_data["rest"][field] # 将path中的参数替换成rest
if "#{%s}" % field in path: # 兼容老版本#{name}
path = path.replace("#{%s}" % field, result)
else:
path = path.replace("{%s}" % field, result)
self.path = path
def collect_controller(self, api_data):
if "sleepBeforeRun" not in api_data["controller"]:
api_data["controller"]["sleepBeforeRun"] = 0 # 默认执行前不等待
if "sleepAfterRun" not in api_data["controller"]:
api_data["controller"]["sleepAfterRun"] = 0 # 默认执行完成不等待
if "useSession" not in api_data["controller"]:
api_data["controller"]["useSession"] = "false" # 默认不使用session
if "saveSession" not in api_data["controller"]:
api_data["controller"]["saveSession"] = "false" # 默认不保存session
if "pre" not in api_data["controller"]:
api_data["controller"]["pre"] = None # 默认没有前置脚本和sql
if "post" not in api_data["controller"]:
api_data["controller"]["post"] = None # 默认没有后置脚本和sql
if "errorContinue" not in api_data["controller"]:
api_data["controller"]["errorContinue"] = "false" # 默认错误后不再执行
self.controller = api_data["controller"]
def collect_conditions(self, api_data):
if "whetherExec" in api_data["controller"]:
self.conditions = json.loads(api_data["controller"]["whetherExec"])
def collect_looper(self, api_data):
if "loopExec" in api_data["controller"]:
self.looper = json.loads(api_data["controller"]["loopExec"])
def collect_query(self, api_data):
if len(api_data["query"]) > 0:
self.others["params"] = api_data["query"]
else:
self.others["params"] = None
def collect_headers(self, api_data):
self.collect_other(api_data, 'headers')
def collect_cookies(self, api_data):
if self.others['headers'] is not None:
pop_key = None
for key in self.others['headers']:
if key.strip().lower() in ['cookie', 'cookies']:
pop_key = key
break
if pop_key is not None:
value = self.others['headers'].pop(pop_key)
self.others['headers']['cookie'] = value
def collect_proxies(self, api_data):
self.collect_other(api_data, 'proxies', proxies_join)
def collect_body(self, api_data):
body = api_data["body"]
if body is None:
return
self.body_type = body["type"]
if body["type"] == "json":
if body["json"] != '':
body_json = json.loads(body["json"])
if len(body_json) > 0:
self.others["json"] = body_json
elif body["type"] in ("form-urlencoded", "form-data"):
body_data, body_file = handle_form_data(body["form"])
if len(body_data) > 0:
self.others["data"] = body_data
if len(body_file) > 0:
self.others["files"] = body_file
elif body["type"] in ("text", "xml", "html"):
if body["raw"] != "":
self.others["data"] = body["raw"]
elif body["type"] == "file":
files = handle_files(body["file"])
if len(files) > 0:
self.others["files"] = files
def collect_stream(self, api_data):
if "requireStream" in api_data["controller"]:
if api_data["controller"]["requireStream"].lower() == "true":
self.others["stream"] = True
else:
self.others["stream"] = False
else:
self.others["stream"] = None
def collect_verify(self, api_data):
if "requireVerify" in api_data["controller"]:
if api_data["controller"]["requireVerify"].lower() == "true":
self.others["verify"] = True
else:
self.others["verify"] = False
else:
self.others["verify"] = None
def collect_auth(self, api_data):
pass
def collect_timeout(self, api_data):
if "timeout" in api_data["controller"]:
self.others["timeout"] = int(api_data["controller"]["timeout"])
else:
self.others["timeout"] = None
def collect_allow_redirects(self, api_data):
pass
def collect_hooks(self, api_data):
pass
def collect_cert(self, api_data):
pass
def collect_assertions(self, api_data):
self.collect_context(api_data, 'assertions')
def collect_relations(self, api_data):
self.collect_context(api_data, 'relations')
def collect(self, api_data):
self.collect_id(api_data)
self.collect_name(api_data)
self.collect_method(api_data)
self.collect_url(api_data)
self.collect_path(api_data)
self.collect_controller(api_data)
self.collect_headers(api_data)
self.collect_cookies(api_data)
self.collect_proxies(api_data)
self.collect_query(api_data)
self.collect_body(api_data)
self.collect_verify(api_data)
self.collect_stream(api_data)
self.collect_auth(api_data)
self.collect_timeout(api_data)
self.collect_allow_redirects(api_data)
self.collect_hooks(api_data)
self.collect_cert(api_data)
self.collect_assertions(api_data)
self.collect_relations(api_data)
class UnDefinableMethodError(Exception):
"""未定义请求方法"""
class UnDefinablePathError(Exception):
"""未定义请求路径"""
class NotExistedFieldError(Exception):
"""未定义必须字段"""
class NotExistedFileUploadType(Exception):
"""未定义的文件上传方式"""
================================================
FILE: core/api/testcase.py
================================================
import re
import sys
from core.api.collector import ApiRequestCollector
from core.template import Template
from core.api.teststep import ApiTestStep, dict2str
from jsonpath_ng.parser import JsonPathParser
from tools.utils.utils import get_case_message, get_json_relation, handle_params_data
class ApiTestCase:
def __init__(self, test):
self.test = test
self.case_message = get_case_message(test.test_data)
self.session = test.session
self.context = test.context
self.id = self.case_message['caseId']
self.name = self.case_message['caseName']
setattr(test, 'test_case_name', self.case_message['caseName'])
setattr(test, 'test_case_desc', self.case_message['comment'])
self.functions = self.case_message['functions']
self.params = handle_params_data(self.case_message['params'])
self.template = Template(self.test, self.context, self.functions, self.params)
self.json_path_parser = JsonPathParser()
self.comp = re.compile(r"\{\{.*?\}\}")
def execute(self):
"""用例执行入口函数"""
if self.case_message['apiList'] is None:
raise RuntimeError("无法获取API相关数据, 请重试!!!")
self.loop_execute(self.case_message['apiList'], "root")
def loop_execute(self, api_list, loop_id, step_n=0):
"""循环执行"""
while step_n < len(api_list):
api_data = api_list[step_n]
# 定义收集器
collector = ApiRequestCollector()
step = ApiTestStep(self.test, self.session, collector, self.context, self.params)
# 循环控制器
step.collector.collect_looper(api_data)
if len(step.collector.looper) > 0 and not (loop_id != "root" and step_n == 0):
# 非根循环 且并非循环第一个接口时才执行循环 从而避免循环套循环情况下的死循环
step.looper_controller(self, api_list, step_n)
step_n = step_n + step.collector.looper["num"] # 跳过本次循环中执行的接口
continue # 母循环最后一个接口索引必须超过子循环的最后一个接口索引 否则超过母循环的接口无法执行
step_n += 1
# 定义事务
self.test.defineTrans(api_data['apiId'], api_data['apiName'], api_data['path'], api_data['apiDesc'])
# 条件控制器
step.collector.collect_conditions(api_data)
if len(step.collector.conditions) > 0:
result = step.condition_controller(self)
if result is not True:
self.test.updateTransStatus(3) # 任意条件不满足 跳过执行
self.test.debugLog('[{}]接口条件控制器判断为否: {}'.format(api_data['apiName'], result))
continue
# 收集请求主体并执行
step.collector.collect(api_data)
try:
# 执行前置脚本和sql
if step.collector.controller["pre"] is not None:
for pre in step.collector.controller["pre"]:
if pre['name'] == 'preScript':
step.exec_script(pre["value"])
else:
step.exec_sql(pre["value"], self)
# 渲染主体
self.render_content(step)
# 执行step, 接口参数移除,接口请求,接口响应,断言操作,依赖参数提取
step.execute()
# 执行后置脚本和sql
if step.collector.controller["post"] is not None:
for post in step.collector.controller["post"]:
if post['name'] == 'postScript':
step.exec_script(post["value"])
else:
step.exec_sql(post["value"], self)
# 检查step的断言结果
if step.assert_result['result']:
self.test.debugLog('[{}]接口断言成功: {}'.format(step.collector.apiName,
dict2str(step.assert_result['checkMessages'])))
else:
self.test.errorLog('[{}]接口断言失败: {}'.format(step.collector.apiName,
dict2str(step.assert_result['checkMessages'])))
raise AssertionError(dict2str(step.assert_result['checkMessages']))
except Exception as e:
error_info = sys.exc_info()
if collector.controller["errorContinue"].lower() == "true":
# 失败后继续执行
if issubclass(error_info[0], AssertionError):
self.test.recordFailStatus(error_info)
else:
self.test.recordErrorStatus(error_info)
else:
raise e
def render_looper(self, looper):
self.template.init(looper)
_looper = self.template.render()
if "times" in _looper:
try:
times = int(_looper["times"])
except:
times = 1
_looper["times"] = times
return _looper
def render_conditions(self, conditions):
self.template.init(conditions)
return self.template.render()
def render_sql(self, sql):
self.template.init(sql)
return self.template.render()
def render_content(self, step):
self.template.init(step.collector.path)
step.collector.path = self.template.render()
if step.collector.others.get('headers') is not None:
headers = step.collector.others.pop('headers')
else:
headers = None
if step.collector.others.get('params') is not None:
query = step.collector.others.pop('params')
else:
query = None
if step.collector.others.get('data') is not None:
body = step.collector.others.pop('data')
pop_key = 'data'
elif step.collector.others.get('json') is not None:
body = step.collector.others.pop('json')
pop_key = 'json'
else:
body = None
pop_key = None
self.template.init(step.collector.others)
step.collector.others = self.template.render()
self.template.set_help_data(step.collector.url, step.collector.path, headers, query, body)
if "#{_request_query" in str(headers).lower() or "#{_request_body" in str(headers).lower():
if "#{_request_body" in str(query).lower():
self.render_json(step, body, "body", pop_key)
self.render_json(step, query, "query")
self.render_json(step, headers, "headers")
else:
self.render_json(step, query, "query")
self.render_json(step, body, "body", pop_key)
self.render_json(step, headers, "headers")
else:
if "#{_request_body" in str(query).lower():
self.render_json(step, headers, "headers")
self.render_json(step, body, "body", pop_key)
self.render_json(step, query, "query")
else:
self.render_json(step, headers, "headers")
self.render_json(step, query, "query")
self.render_json(step, body, "body", pop_key)
if step.collector.assertions is not None:
self.template.init(step.collector.assertions)
step.collector.assertions = self.template.render()
if step.collector.relations is not None:
self.template.init(step.collector.relations)
step.collector.relations = self.template.render()
def render_json(self, step, data, name, pop_key=None):
if data is None:
return
if name == "body" and step.collector.body_type not in ("json", "form-urlencoded", "form-data"):
self.template.init(data)
render_value = self.template.render()
self.template.request_body = render_value
else:
for expr, value in get_json_relation(data, name):
if isinstance(value, str) and self.comp.search(value) is not None:
self.template.init(value)
render_value = self.template.render()
if name == "headers":
render_value = str(render_value)
expression = self.json_path_parser.parse(expr)
expression.update(data, render_value)
if name == "body":
self.template.request_body = data
elif name == "query":
self.template.request_query = data
else:
self.template.request_headers = data
if name == "body":
step.collector.others.setdefault(pop_key, self.template.request_body)
elif name == "query":
step.collector.others.setdefault("params", self.template.request_query)
else:
step.collector.others.setdefault("headers", self.template.request_headers)
================================================
FILE: core/api/teststep.py
================================================
import datetime
import sys
from time import sleep
from requests import request, Session
from copy import deepcopy
import json
from core.assertion import LMAssert
from tools.utils.sql import SQLConnect
from tools.utils.utils import extract, ExtractValueError, url_join
from urllib.parse import urlencode
REQUEST_CNAME_MAP = {
'headers': '请求头',
'proxies': '代理',
'cookies': 'cookies',
'params': '查询参数',
'data': '请求体',
'json': '请求体',
'files': '上传文件'
}
class ApiTestStep:
def __init__(self, test, session, collector, context, params):
self.session = session
self.collector = collector
self.context = context
self.params = params
self.test = test
self.status_code = None
self.response_request = None
self.response_headers = None
self.response_content = None
self.response_content_bytes = None
self.response_cookies = None
self.assert_result = None
self.print = print
def execute(self):
try:
self.test.debugLog('[{}]接口执行开始'.format(self.collector.apiName))
request_log = '【请求信息】:<br>'
request_log += '{} {}<br>'.format(self.collector.method, url_join(self.collector.url, self.collector.path))
for key, value in self.collector.others.items():
if value is not None:
c_key = REQUEST_CNAME_MAP[key] if key in REQUEST_CNAME_MAP else key
if key == 'files':
if isinstance(value, dict):
request_log += '{}: {}<br>'.format(c_key, ["文件长度%s: %s" % (k, len(v)) for k,v in value.items()])
if isinstance(value, list):
request_log += '{}: {}<br>'.format(c_key, [i[1][0] for i in value])
elif c_key == '请求体':
request_log += '<span>{}: {}</span><br>'.format(c_key, dict2str(value))
else:
request_log += '{}: {}<br>'.format(c_key, dict2str(value))
self.test.debugLog(request_log[:-4])
if self.collector.body_type == "form-urlencoded" and 'data' in self.collector.others:
self.collector.others['data'] = urlencode(self.collector.others['data'])
if self.collector.body_type in ("text", "xml", "html") and 'data' in self.collector.others:
self.collector.others['data'] = str(self.collector.others['data']).encode("utf-8")
if 'files' in self.collector.others and self.collector.others['files'] is not None:
self.pop_content_type()
url = url_join(self.collector.url, self.collector.path)
if int(self.collector.controller["sleepBeforeRun"]) > 0:
sleep(int(self.collector.controller["sleepBeforeRun"]))
self.test.debugLog("请求前等待%sS" % int(self.collector.controller["sleepBeforeRun"]))
start_time = datetime.datetime.now()
if self.collector.controller["useSession"].lower() == 'true' and self.collector.controller["saveSession"].lower() == "true":
res = self.session.session.request(self.collector.method, url, **self.collector.others)
elif self.collector.controller["useSession"].lower() == "true":
session = deepcopy(self.session.session)
res = session.request(self.collector.method, url, **self.collector.others)
elif self.collector.controller["saveSession"].lower() == "true":
session = Session()
res = session.request(self.collector.method, url, **self.collector.others)
self.session.session = session
else:
res = request(self.collector.method, url, **self.collector.others)
end_time = datetime.datetime.now()
self.response_request = res.request
self.test.recordTransDuring(int((end_time-start_time).microseconds/1000))
self.save_response(res)
response_log = '【响应信息】:<br>'
response_log += '响应码: {}<br>'.format(self.status_code)
response_log += '响应头: {}<br>'.format(dict2str(self.response_headers))
if 'content-disposition' not in [key.lower() for key in self.response_headers.keys()]:
response_text = '<b>响应体: {}</b>'.format(dict2str(self.response_content))
else:
response_text = '<b>响应体: 文件内容暂不展示, 长度{}</b>'.format(len(self.response_content_bytes))
response_log += response_text
self.test.debugLog(response_log)
# 断言
self.check()
# 关联参数
self.extract_depend_params()
finally:
self.test.debugLog('[{}]接口执行结束'.format(self.collector.apiName))
if int(self.collector.controller["sleepAfterRun"]) > 0:
sleep(int(self.collector.controller["sleepAfterRun"]))
self.test.debugLog("请求后等待%sS" % int(self.collector.controller["sleepAfterRun"]))
def looper_controller(self, case, api_list, step_n):
"""循环控制器"""
if "type" in self.collector.looper and self.collector.looper["type"] == "WHILE":
# while循环 且兼容之前只有for循环
loop_start_time = datetime.datetime.now()
while self.collector.looper["timeout"] == 0 or (datetime.datetime.now() - loop_start_time).seconds * 1000 \
< self.collector.looper["timeout"]: # timeout为0时可能会死循环 慎重选择
# 渲染循环控制控制器 每次循环都需要渲染
_looper = case.render_looper(self.collector.looper)
result, _ = LMAssert(_looper['assertion'], _looper['target'], _looper['expect']).compare()
if not result:
break
_api_list = api_list[step_n: (step_n + _looper["num"])]
case.loop_execute(_api_list, api_list[step_n]["apiId"])
else:
# 渲染循环控制控制器 for只需渲染一次
_looper = case.render_looper(self.collector.looper)
for index in range(_looper["times"]): # 本次循环次数
self.context[_looper["indexName"]] = index # 给循环索引赋值第几次循环 母循环和子循环的索引名不应一样
_api_list = api_list[step_n: (step_n + _looper["num"])]
case.loop_execute(_api_list, api_list[step_n]["apiId"])
def condition_controller(self, case):
"""条件控制器"""
_conditions = case.render_conditions(self.collector.conditions)
for condition in _conditions:
try:
result, msg = LMAssert(condition['assertion'], condition['target'], condition['expect']).compare()
if not result:
return msg
except Exception as e:
return str(e)
else:
return True
def exec_script(self, code):
"""执行前后置脚本"""
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = self.test.stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_put(name, val, ps=False):
if ps: # 默认给关联参数赋值,只有多传入true时才会给公参赋值
self.params[name] = val
else:
self.context[name] = val
def sys_get(name):
if name in self.context: # 优先从公参中取值
return self.context[name]
elif name in self.params:
return self.params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
names = locals()
names["res_request"] = self.response_request
names["res_code"] = self.status_code
names["res_header"] = self.response_headers
names["res_data"] = self.response_content
names["res_cookies"] = self.response_cookies
names["res_bytes"] = self.response_content_bytes
exec(code)
def exec_sql(self, sql, case):
"""执行前后置sql"""
if sql == "{}":
return
sql = json.loads(case.render_sql(sql))
if "host" not in sql["db"]:
raise KeyError("获取数据库连接信息失败 请检查配置")
conn = SQLConnect(**sql["db"])
if sql["sqlType"] != "query":
conn.exec(sql["sqlText"])
else:
results = conn.query(sql["sqlText"])
names = sql["names"].split(",") # name数量可以比结果数量段,但不能长,不能会indexError
for j, n in enumerate(names):
if len(results) == 0:
self.context[n] = [] # 如果查询结果为空 则变量保存为空数组
continue
if j >= len(results):
raise IndexError("变量数错误, 请检查变量数配置是否与查询语句一致,当前查询结果: <br>{}".format(results))
self.context[n] = results[j] # 保存变量到变量空间
def save_response(self, res):
"""保存响应结果"""
self.status_code = res.status_code
self.response_headers = dict(res.headers)
self.response_content_bytes = res.content
s = ''
for key, value in res.cookies.items():
s += '{}={};'.format(key, value)
self.response_cookies = s[:-1]
try:
self.response_content = res.json()
except Exception:
self.response_content = res.text
def extract_depend_params(self):
"""关联参数"""
if self.collector.relations is not None:
for items in self.collector.relations:
if items['expression'].strip() == '$':
value = self.response_content_bytes
elif items['expression'].strip().lower() in ['cookie', 'cookies']:
value = self.response_cookies
else:
if items['from'] == 'resHeader':
data = self.response_headers
elif items['from'] == 'resBody':
data = self.response_content
elif items['from'] == 'reqHeader':
data = self.collector.others['headers']
elif items['from'] == 'reqQuery':
data = self.collector.others['params']
elif items['from'] == 'reqBody':
if self.collector.body_type == "json":
data = self.collector.others['json']
else:
data = self.collector.others['data']
else:
raise ExtractValueError('无法从{}位置提取依赖参数'.format(items['from']))
value = extract(items['method'], data, items['expression'])
key = items['name']
self.context[key] = value
def check(self):
"""断言"""
check_messages = list()
if self.collector.assertions is not None:
results = list()
for items in self.collector.assertions:
try:
if items['from'] == 'resCode':
actual = self.status_code
elif items['from'] == 'resHeader':
actual = extract(items['method'], self.response_headers, items['expression'])
elif items['from'] == 'resBody':
actual = extract(items['method'], self.response_content, items['expression'])
else:
raise ExtractValueError('无法在{}位置进行断言'.format(items['from']))
result, msg = LMAssert(items['assertion'], actual, items['expect']).compare()
except ExtractValueError as e:
result = False
msg = '接口响应失败或{}'.format(str(e))
results.append(result)
check_messages.append(msg)
if not result:
break
final_result = all(results)
else:
final_result, msg = LMAssert('相等', self.status_code, str(200)).compare()
check_messages.append(msg)
self.assert_result = {
'apiId': self.collector.apiId,
'apiName': self.collector.apiName,
'result': final_result,
'checkMessages': check_messages
}
def pop_content_type(self):
if self.collector.others['headers'] is None:
return
pop_key = None
for key, value in self.collector.others['headers'].items():
if key.lower() == 'content-type':
pop_key = key
break
if pop_key is not None:
self.collector.others['headers'].pop(pop_key)
def dict2str(data):
if not isinstance(data, str):
return str(data)
else:
return data
class RemoveParamError(Exception):
"""参数移除错误"""
class AssertRelationError(Exception):
"""断言关系错误"""
================================================
FILE: core/app/collector.py
================================================
import json
class AppOperationCollector:
def __init__(self):
self.id = None
self.opt_type = None
self.opt_system = None
self.opt_name = None
self.opt_trans = None
self.opt_element = None
self.opt_data = None
self.opt_code = None
@staticmethod
def __parse(ui_data: dict, name):
if name not in ui_data:
return None
return ui_data.get(name)
def collect_id(self, ui_data):
self.id = AppOperationCollector.__parse(ui_data, "operationId")
def collect_opt_type(self, ui_data):
self.opt_type = AppOperationCollector.__parse(ui_data, "operationType")
def collect_opt_system(self, ui_data):
self.opt_system = AppOperationCollector.__parse(ui_data, "operationSystem")
def collect_opt_name(self, ui_data):
self.opt_name = AppOperationCollector.__parse(ui_data, "operationName")
def collect_opt_trans(self, ui_data):
self.opt_trans = AppOperationCollector.__parse(ui_data, "operationTrans")
def collect_opt_code(self, ui_data):
self.opt_code = AppOperationCollector.__parse(ui_data, "operationCode")
def collect_opt_element(self, ui_data):
opt_element = AppOperationCollector.__parse(ui_data, "operationElement")
if opt_element is None or len(opt_element) == 0:
self.opt_element = None
else:
elements = {}
for name, element in opt_element.items():
props = {}
if element["by"].lower() == "prop":
for prop in json.loads(element["expression"]):
props[prop["propName"]] = prop["propValue"]
elif element["by"].lower() == "pred":
props["predicate"] = element["expression"]
elif element["by"].lower() == "class":
props["classChain"] = element["expression"]
else:
props[element["by"].lower()] = element["expression"]
elements[name] = props
self.opt_element = elements
def collect_opt_data(self, ui_data):
opt_data = AppOperationCollector.__parse(ui_data, "operationData")
if opt_data is None or len(opt_data) == 0:
self.opt_data = None
else:
self.opt_data = opt_data
def collect(self, ui_data):
self.collect_id(ui_data)
self.collect_opt_type(ui_data)
self.collect_opt_system(ui_data)
self.collect_opt_name(ui_data)
self.collect_opt_trans(ui_data)
self.collect_opt_element(ui_data)
self.collect_opt_data(ui_data)
self.collect_opt_code(ui_data)
================================================
FILE: core/app/device/__init__.py
================================================
from typing import Optional
from uiautomator2 import Device
from wda import Client, AlertAction, BaseClient
class AndroidDriver(Device):
"""安卓设备"""
def __call__(self, **kwargs):
if len(kwargs) == 1 and "xpath" in kwargs:
return self.xpath(kwargs["xpath"])
else:
return Device.__call__(self, **kwargs)
def find_element(self, **kwargs):
if len(kwargs) == 1 and "xpath" in kwargs:
return self.xpath(kwargs["xpath"])
else:
return Device.__call__(self, **kwargs)
class AppleDevice(Client):
"""苹果设备"""
def session(self,
bundle_id=None,
arguments: Optional[list] = None,
environment: Optional[dict] = None,
alert_action: Optional[AlertAction] = None):
setattr(Client, 'find_element', AppleDevice.find_element)
client = BaseClient.session(self, bundle_id, arguments, environment, alert_action)
return client
def find_element(self, **kwargs):
return BaseClient.__call__(self, **kwargs)
def connect_device(system: str, url: str):
if system.lower() == "android":
return AndroidDriver(url)
else:
return AppleDevice(url)
class Operation(object):
def __init__(self, test, device):
self.device = device
self.test = test
self.print = print
def find_element(self, ele):
"""查找单个元素"""
try:
element = self.device.find_element(**ele)
self.test.debugLog("定位元素: %s" % str(ele))
return element
except Exception as e:
self.test.errorLog("定位元素出错: %s" % str(ele))
raise e
class ElementNotFoundError(Exception):
"""元素获取失败"""
class ElementNotDisappearError(Exception):
"""元素消失失败"""
================================================
FILE: core/app/device/assertionOpt.py
================================================
import sys
from uiautomator2 import UiObjectNotFoundError
from core.assertion import LMAssert
from core.app.device import Operation
class Assertion(Operation):
"""断言类操作"""
def assert_ele_exists(self, element, assertion, expect):
"""断言元素存在"""
try:
actual = self.find_element(element).exists
self.test.debugLog("成功获取元素exists:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素exists")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_text(self, system, element, assertion, expect):
"""断言元素文本"""
try:
if system == "android":
actual = self.find_element(element).get_text()
else:
actual = self.find_element(element).text
self.test.debugLog("成功获取元素text:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素text")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_attribute(self, element, attribute, assertion, expect):
"""断言元素属性"""
try:
actual = self.find_element(element).info[attribute]
self.test.debugLog("成功获取元素%s属性:%s" % (attribute, str(actual)))
except Exception as e:
self.test.errorLog("无法获取元素%s属性" % attribute)
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_center(self, system, element, assertion, expect):
"""断言元素位置"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = (x, y)
else:
x, y = self.find_element(element).bounds.center
actual = (x, y)
self.test.debugLog("成功获取元素位置:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素位置")
raise e
else:
result, msg = LMAssert(assertion, str(actual), expect).compare()
return result, msg
def assert_ele_x(self, system, element, assertion, expect):
"""断言元素X坐标"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = x
else:
x, y = self.find_element(element).bounds.center
actual = x
self.test.debugLog("成功获取元素X坐标:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素X坐标")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_y(self, system, element, assertion, expect):
"""断言元素Y坐标"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = y
else:
x, y = self.find_element(element).bounds.center
actual = y
self.test.debugLog("成功获取元素Y坐标:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素Y坐标")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_alert_exists(self, assertion, expect):
"""断言弹框存在 IOS专属"""
try:
actual = self.device.alert.exists
self.test.debugLog("成功获取弹框exists:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取弹框exists")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_alert_text(self, assertion, expect):
"""断言弹框文本 IOS专属"""
try:
actual = self.device.alert.text
self.test.debugLog("成功获取弹框文本:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取弹框文本")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["device"] = self.device
names["test"] = self.test
try:
"""断言操作需要返回被断言的值 以sys_return(value)返回"""
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_return(res):
names["_exec_result"] = res
def sys_get(name):
if name in names["test"].context:
return names["test"].context[name]
elif name in names["test"].common_params:
return names["test"].common_params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
def sys_put(name, val, ps=False):
if ps:
names["test"].common_params[name] = val
else:
names["test"].context[name] = val
exec(code)
self.test.debugLog("成功执行 %s" % kwargs["trans"])
except UiObjectNotFoundError as e:
raise e
except Exception as e:
self.test.errorLog("无法执行 %s" % kwargs["trans"])
raise e
else:
result, msg = LMAssert(kwargs["data"]["assertion"], names["_exec_result"], kwargs["data"]["expect"]).compare()
return result, msg
================================================
FILE: core/app/device/conditionOpt.py
================================================
import sys
from uiautomator2 import UiObjectNotFoundError
from core.assertion import LMAssert
from core.app.device import Operation
class Condition(Operation):
"""条件类操作"""
def condition_ele_exists(self, element, assertion, expect):
"""判断元素存在"""
try:
actual = self.find_element(element).exists
self.test.debugLog("成功获取元素exists:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素exists")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_text(self, system, element, assertion, expect):
"""判断元素文本"""
try:
if system == "android":
actual = self.find_element(element).get_text()
else:
actual = self.find_element(element).text
self.test.debugLog("成功获取元素text:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素text")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_attribute(self, element, attribute, assertion, expect):
"""判断元素属性"""
try:
actual = self.find_element(element).info[attribute]
self.test.debugLog("成功获取元素%s属性:%s" % (attribute, str(actual)))
except Exception as e:
self.test.errorLog("无法获取元素%s属性" % attribute)
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_center(self, system, element, assertion, expect):
"""判断元素位置"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = (x, y)
else:
size = self.find_element(element).bounds
actual = (size.x, size.y)
self.test.debugLog("成功获取元素位置:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素位置")
raise e
else:
result, msg = LMAssert(assertion, str(actual), expect).compare()
return result, msg
def condition_ele_x(self, system, element, assertion, expect):
"""判断元素X坐标"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = x
else:
x, y = self.find_element(element).bounds.center
actual = x
self.test.debugLog("成功获取元素X坐标:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素X坐标")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_y(self, system, element, assertion, expect):
"""判断元素Y坐标"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = y
else:
x, y = self.find_element(element).bounds.center
actual = y
self.test.debugLog("成功获取元素Y坐标:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素Y坐标")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_alert_exists(self, assertion, expect):
"""判断弹框存在 IOS专属"""
try:
actual = self.device.alert.exists
self.test.debugLog("成功获取弹框exists:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取弹框exists")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_alert_text(self, assertion, expect):
"""判断弹框文本 IOS专属"""
try:
actual = self.device.alert.text
self.test.debugLog("成功获取弹框文本:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取弹框文本")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["device"] = self.device
names["test"] = self.test
try:
"""条件操作需要返回被判断的值 以sys_return(value)返回"""
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_return(res):
names["_exec_result"] = res
def sys_get(name):
if name in names["test"].context:
return names["test"].context[name]
elif name in names["test"].common_params:
return names["test"].common_params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
def sys_put(name, val, ps=False):
if ps:
names["test"].common_params[name] = val
else:
names["test"].context[name] = val
exec(code)
self.test.debugLog("成功执行 %s" % kwargs["trans"])
except UiObjectNotFoundError as e:
raise e
except Exception as e:
self.test.errorLog("无法执行 %s" % kwargs["trans"])
raise e
else:
result, msg = LMAssert(kwargs["data"]["assertion"], names["_exec_result"], kwargs["data"]["expect"]).compare()
return result, msg
================================================
FILE: core/app/device/relationOpt.py
================================================
import sys
from uiautomator2 import UiObjectNotFoundError
from core.app.device import Operation
class Relation(Operation):
"""关联类操作"""
def get_window_size(self, system, save_name):
"""提取屏幕尺寸"""
try:
if system == "android":
w, h = self.device.window_size()
actual = (w, h)
else:
size = self.device.window_size()
actual = (size.width, size.height)
self.test.debugLog("成功获取屏幕尺寸:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取屏幕尺寸")
raise e
else:
self.test.context[save_name] = actual
def get_window_width(self, system, save_name):
"""提取屏幕宽度"""
try:
if system == "android":
w, h = self.device.window_size()
actual = w
else:
size = self.device.window_size()
actual = size.width
self.test.debugLog("成功获取屏幕宽度:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取屏幕宽度")
raise e
else:
self.test.context[save_name] = actual
def get_window_height(self, system, save_name):
"""提取屏幕高度"""
try:
if system == "android":
w, h = self.device.window_size()
actual = h
else:
size = self.device.window_size()
actual = size.height
self.test.debugLog("成功获取屏幕高度:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取屏幕高度")
raise e
else:
self.test.context[save_name] = actual
def get_ele_text(self, system, element, save_name):
"""提取元素文本"""
try:
if system == "android":
actual = self.find_element(element).get_text()
else:
actual = self.find_element(element).text
self.test.debugLog("成功获取元素文本:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素文本")
raise e
else:
self.test.context[save_name] = actual
def get_ele_center(self, system, element, save_name):
"""提取元素位置"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = (x, y)
else:
x, y = self.find_element(element).bounds.center
actual = (x, y)
self.test.debugLog("成功获取元素位置:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素位置")
raise e
else:
self.test.context[save_name] = actual
def get_ele_x(self, system, element, save_name):
"""提取元素X坐标"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = x
else:
x, y = self.find_element(element).bounds.center
actual = x
self.test.debugLog("成功获取元素X坐标:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素X坐标")
raise e
else:
self.test.context[save_name] = actual
def get_ele_y(self, system, element, save_name):
"""提取元素Y坐标"""
try:
if system == "android":
x, y = self.find_element(element).center()
actual = y
else:
x, y = self.find_element(element).bounds.center
actual = y
self.test.debugLog("成功获取元素Y坐标:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素Y坐标")
raise e
else:
self.test.context[save_name] = actual
def get_alert_text(self, save_name):
"""提取弹框文本 IOS专属"""
try:
actual = self.device.alert.text
self.test.debugLog("成功获取弹框文本:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取弹框文本")
raise e
else:
self.test.context[save_name] = actual
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["device"] = self.device
names["test"] = self.test
try:
"""关联操作需要返回被关联的值 以sys_return(value)返回"""
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_return(res):
names["_exec_result"] = res
def sys_get(name):
if name in names["test"].context:
return names["test"].context[name]
elif name in names["test"].common_params:
return names["test"].common_params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
def sys_put(name, val, ps=False):
if ps:
names["test"].common_params[name] = val
else:
names["test"].context[name] = val
exec(code)
self.test.debugLog("成功执行 %s" % kwargs["trans"])
except UiObjectNotFoundError as e:
raise e
except Exception as e:
self.test.errorLog("无法执行 %s" % kwargs["trans"])
raise e
else:
self.test.context[kwargs["data"]["save_name"]] = names["_exec_result"]
================================================
FILE: core/app/device/scenarioOpt.py
================================================
import sys
from uiautomator2 import UiObjectNotFoundError
from core.app.device import Operation
class Scenario(Operation):
"""场景类操作"""
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["device"] = self.device
names["test"] = self.test
try:
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_get(name):
if name in names["test"].context:
return names["test"].context[name]
elif name in names["test"].common_params:
return names["test"].common_params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
def sys_put(name, val, ps=False):
if ps:
names["test"].common_params[name] = val
else:
names["test"].context[name] = val
exec(code)
self.test.debugLog("成功执行 %s" % kwargs["trans"])
except UiObjectNotFoundError as e:
raise e
except Exception as e:
self.test.errorLog("无法执行 %s" % kwargs["trans"])
raise e
================================================
FILE: core/app/device/systemOpt.py
================================================
import sys
from time import sleep
from uiautomator2 import UiObjectNotFoundError
from wda import WDAElementNotFoundError
from core.app.device import Operation
class System(Operation):
"""系统操作"""
def start_app(self, app_id):
"""启动应用"""
try:
self.device.app_start(app_id)
self.test.debugLog("成功执行启动应用")
except Exception as e:
self.test.errorLog("无法执行关闭应用")
raise e
def close_app(self, app_id):
"""关闭应用"""
try:
self.device.app_stop(app_id)
self.test.debugLog("成功执行关闭应用")
except Exception as e:
self.test.errorLog("无法执行关闭应用")
raise e
def swipe_left(self, system):
"""左滑"""
try:
if system == "android":
self.device.swipe_ext("left")
else:
self.device.swipe_left()
self.test.debugLog("成功执行左滑")
except Exception as e:
self.test.errorLog("无法执行左滑")
raise e
def swipe_right(self, system):
"""右滑"""
try:
if system == "android":
self.device.swipe_ext("right")
else:
self.device.swipe_right()
self.test.debugLog("成功执行右滑")
except Exception as e:
self.test.errorLog("无法执行右滑")
raise e
def swipe_up(self, system):
"""上滑"""
try:
if system == "android":
self.device.swipe_ext("up")
else:
self.device.swipe_up()
self.test.debugLog("成功执行上滑")
except Exception as e:
self.test.errorLog("无法执行上滑")
raise e
def swipe_down(self, system):
"""下滑"""
try:
if system == "android":
self.device.swipe_ext("down")
else:
self.device.swipe_down()
self.test.debugLog("成功执行下滑")
except Exception as e:
self.test.errorLog("无法执行下滑")
raise e
def home(self, system):
"""系统首页"""
try:
if system == "android":
self.device.keyevent("home")
else:
self.device.home()
self.test.debugLog("成功执行返回系统首页")
except Exception as e:
self.test.errorLog("无法执行返回系统首页")
raise e
def back(self):
"""系统返回 安卓专用"""
try:
self.device.keyevent("back")
self.test.debugLog("成功执行返回")
except Exception as e:
self.test.errorLog("无法执行返回")
raise e
def press(self, keycode):
"""系统按键"""
try:
self.device.press(keycode)
self.test.debugLog("成功执行按下系统键位: %s" % keycode)
except Exception as e:
self.test.errorLog("无法执行按下系统键位: %s" % keycode)
raise e
def screenshot(self, name):
"""屏幕截图"""
try:
screenshot = self.device.screenshot(format='raw')
self.test.saveScreenShot(name, screenshot)
self.test.debugLog("成功执行屏幕截图")
except Exception as e:
self.test.errorLog("无法执行屏幕截图")
raise e
def screen_on(self, system):
"""亮屏"""
try:
if system == "android":
self.device.screen_on()
else:
self.device.unlock()
self.test.debugLog("成功执行亮屏")
except Exception as e:
self.test.errorLog("无法执行亮屏")
raise e
def screen_off(self, system):
"""息屏"""
try:
if system == "android":
self.device.screen_off()
else:
self.device.lock()
self.test.debugLog("成功执行息屏")
except Exception as e:
self.test.errorLog("无法执行息屏")
raise e
def sleep(self, second):
"""强制等待"""
try:
sleep(second)
self.test.debugLog("成功执行sleep %ds" % second)
except Exception as e:
self.test.errorLog("无法执行sleep %ds" % second)
raise e
def implicitly_wait(self, second):
"""隐式等待"""
try:
self.device.implicitly_wait(second)
self.test.debugLog("成功执行implicitly wait %ds" % second)
except Exception as e:
self.test.errorLog("无法执行implicitly wait %ds" % second)
raise e
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["device"] = self.device
names["test"] = self.test
try:
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_get(name):
if name in names["test"].context:
return names["test"].context[name]
elif name in names["test"].common_params:
return names["test"].common_params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
def sys_put(name, val, ps=False):
if ps:
names["test"].common_params[name] = val
else:
names["test"].context[name] = val
exec(code)
self.test.debugLog("成功执行 %s" % kwargs["trans"])
except UiObjectNotFoundError as e:
raise e
except WDAElementNotFoundError as e:
raise e
except Exception as e:
self.test.errorLog("无法执行 %s" % kwargs["trans"])
raise e
================================================
FILE: core/app/device/viewOpt.py
================================================
import sys
from uiautomator2 import UiObjectNotFoundError
from uiautomator2.xpath import XPath
from wda import WDAElementNotFoundError
from core.app.device import Operation, ElementNotFoundError, ElementNotDisappearError
class View(Operation):
"""视图类操作"""
def click(self, element):
"""单击"""
try:
self.find_element(element).click_exists(timeout=3)
self.test.debugLog("成功单击")
except Exception as e:
self.test.errorLog("无法单击")
raise e
def double_click(self, system, element):
"""双击"""
try:
if system == "android":
self.device.double_click(*self.find_element(element).center())
else:
self.device.double_tap(*self.find_element(element).center())
self.test.debugLog("成功双击")
except Exception as e:
self.test.errorLog("无法双击")
raise e
def long_click(self, system, element, second):
"""长按"""
try:
if system == "android":
if "xpath" in element:
self.find_element(element).long_click()
else:
self.find_element(element).long_click(second)
else:
self.find_element(element).tap_hold(second)
self.test.debugLog("成功长按%sS" % str(second))
except Exception as e:
self.test.errorLog("无法长按%sS" % str(second))
raise e
def click_coord(self, x, y):
"""坐标单击 百分比或坐标值"""
try:
self.device.click(x, y)
self.test.debugLog("成功坐标单击")
except Exception as e:
self.test.errorLog("无法坐标单击")
raise e
def double_click_coord(self, system, x, y):
"""坐标双击 百分比或坐标值"""
try:
if system == "android":
self.device.double_click(x, y)
else:
self.device.double_tap(x, y)
self.test.debugLog("成功坐标双击")
except Exception as e:
self.test.errorLog("无法坐标双击")
raise e
def long_click_coord(self, system, x, y, second):
"""坐标长按 百分比或坐标值"""
try:
if system == "android":
self.device.long_click(x, y, second)
else:
self.device.tap_hold(x, y, second)
self.test.debugLog("成功坐标长按%sS" % str(second))
except Exception as e:
self.test.errorLog("无法坐标长按%sS" % str(second))
raise e
def swipe(self, system, fx, fy, tx, ty, duration=None):
"""坐标滑动 百分比或坐标值"""
try:
if system == "android":
if duration == "":
duration = None
self.device.swipe(fx, fy, tx, ty, duration)
else:
if duration == "" or duration is None:
duration = 0
self.device.swipe(fx, fy, tx, ty, duration)
self.test.debugLog("成功执行滑动")
except Exception as e:
self.test.errorLog("无法执行滑动")
raise e
def input_text(self, element, text):
"""输入"""
try:
self.find_element(element).set_text(text)
self.test.debugLog("成功输入%s" % str(text))
except Exception as e:
self.test.errorLog("无法输入%s" % str(text))
raise e
def clear_text(self, system, element):
"""清空"""
try:
ele = self.find_element(element)
if system == "android" and len(element) == 1 and "xpath" in element:
xe = ele.get()
ele._d.set_fastinput_ime()
xe.click()
ele._parent._d.set_fastinput_ime()
ele._parent._d.clear_text()
else:
ele.clear_text()
self.test.debugLog("成功清空")
except Exception as e:
self.test.errorLog("无法清空")
raise e
def scroll_to_ele(self, system, element, direction):
"""滑动到元素出现"""
try:
if system == "android":
if "xpath" in element:
XPath(self.device).scroll_to(element["xpath"], direction)
elif direction == "up":
self.device(scrollable=True).forward.to(**element)
elif direction == "down":
self.device(scrollable=True).backward.to(**element)
elif direction == "left":
self.device(scrollable=True).horiz.forward.to(**element)
else:
self.device(scrollable=True).horiz.backward.to(**element)
else:
self.find_element(element).scroll(direction)
self.test.debugLog("成功滑动到元素出现")
except Exception as e:
self.test.errorLog("无法滑动到元素出现")
raise e
def pinch_in(self, system, element):
"""缩小 安卓仅支持属性定位"""
try:
if system == "android":
self.find_element(element).pinch_in()
else:
self.find_element(element).pinch(0.5, -1)
self.test.debugLog("成功缩小")
except Exception as e:
self.test.errorLog("无法缩小")
raise e
def pinch_out(self, system, element):
"""放大 安卓仅支持属性定位"""
try:
if system == "android":
self.find_element(element).pinch_out()
else:
self.find_element(element).pinch(2, 1)
self.test.debugLog("成功放大")
except Exception as e:
self.test.errorLog("无法放大")
raise e
def wait(self, element, second):
"""等待元素出现"""
try:
if self.find_element(element).wait(timeout=second):
self.test.debugLog("成功等待元素出现")
else:
self.test.errorLog("等待元素出现失败 元素不存在")
raise ElementNotFoundError("element not exists")
except ElementNotFoundError as e:
raise e
except Exception as e:
self.test.errorLog("无法等待元素出现")
raise e
def wait_gone(self, system, element, second):
"""等待元素消失"""
try:
if system == "android":
res = self.find_element(element).wait_gone(timeout=second)
else:
res = self.find_element(element).wait_gone(timeout=second, raise_error=False)
if res:
self.test.debugLog("成功等待元素消失")
else:
self.test.errorLog("等待元素消失失败 元素仍存在")
raise ElementNotDisappearError("element exists")
except ElementNotDisappearError as e:
raise e
except Exception as e:
self.test.errorLog("无法等待元素消失")
raise e
def drag_to_ele(self, start_element, end_element):
"""拖动到元素 安卓专属 只支持属性定位"""
try:
self.find_element(start_element).drag_to(**end_element)
self.test.debugLog("成功拖动到元素")
except Exception as e:
self.test.errorLog("无法拖动到元素")
raise e
def drag_to_coord(self, element, x, y):
"""拖动到坐标 安卓专属 只支持属性定位"""
try:
self.find_element(element).drag_to(x, y)
self.test.debugLog("成功拖动到坐标")
except Exception as e:
self.test.errorLog("无法拖动到坐标")
raise e
def drag_coord(self, fx, fy, tx, ty):
"""坐标拖动 安卓专属"""
try:
self.device.drag(fx, fy, tx, ty)
self.test.debugLog("成功坐标拖动")
except Exception as e:
self.test.errorLog("无法坐标拖动")
raise e
def swipe_ele(self, element, direction):
"""元素内滑动 安卓专属"""
try:
self.find_element(element).swipe(direction)
self.test.debugLog("成功元素内滑动")
except Exception as e:
self.test.errorLog("无法元素内滑动")
raise e
def alert_wait(self, second):
"""等待弹框出现 IOS专属"""
try:
self.device.alert.wait(second)
self.test.debugLog("成功等待弹框出现")
except Exception as e:
self.test.errorLog("无法等待弹框出现")
raise e
def alert_accept(self):
"""弹框确认 IOS专属"""
try:
self.device.alert.accept()
self.test.debugLog("成功弹框确认")
except Exception as e:
self.test.errorLog("无法弹框确认")
raise e
def alert_dismiss(self):
"""弹框取消 IOS专属"""
try:
self.device.alert.dismiss()
self.test.debugLog("成功弹框取消")
except Exception as e:
self.test.errorLog("无法弹框取消")
raise e
def alert_click(self, name):
"""弹框点击 IOS专属"""
try:
self.device.alert.click(name)
self.test.debugLog("成功弹框点击%s" % name)
except Exception as e:
self.test.errorLog("无法弹框点击%s" % name)
raise e
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["device"] = self.device
names["test"] = self.test
try:
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_get(name):
if name in names["test"].context:
return names["test"].context[name]
elif name in names["test"].common_params:
return names["test"].common_params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
def sys_put(name, val, ps=False):
if ps:
names["test"].common_params[name] = val
else:
names["test"].context[name] = val
exec(code)
self.test.debugLog("成功执行 %s" % kwargs["trans"])
except UiObjectNotFoundError as e:
raise e
except WDAElementNotFoundError as e:
raise e
except Exception as e:
self.test.errorLog("无法执行 %s" % kwargs["trans"])
raise e
================================================
FILE: core/app/find_opt.py
================================================
from core.app.device.viewOpt import View
from core.app.device.systemOpt import System
from core.app.device.scenarioOpt import Scenario
from core.app.device.assertionOpt import Assertion
from core.app.device.relationOpt import Relation
from core.app.device.conditionOpt import Condition
def find_system_opt(operate_name: str):
function = None
def keywords(name):
def back(func):
if name == operate_name:
nonlocal function
function = func
return back
@keywords("启动应用")
def start_app(test, device, **kwargs):
System(test, device).start_app(kwargs["data"]["appId"])
@keywords("关闭应用")
def close_app(test, device, **kwargs):
System(test, device).close_app(kwargs["data"]["appId"])
@keywords("左滑")
def swipe_left(test, device, **kwargs):
System(test, device).swipe_left(kwargs["system"])
@keywords("右滑")
def swipe_right(test, device, **kwargs):
System(test, device).swipe_right(kwargs["system"])
@keywords("上滑")
def swipe_up(test, device, **kwargs):
System(test, device).swipe_up(kwargs["system"])
@keywords("下滑")
def swipe_down(test, device, **kwargs):
System(test, device).swipe_down(kwargs["system"])
@keywords("系统首页")
def home(test, device, **kwargs):
System(test, device).home(kwargs["system"])
@keywords("系统返回")
def back(test, device, **kwargs):
System(test, device).back()
@keywords("系统按键")
def press(test, device, **kwargs):
System(test, device).press(kwargs["data"]["keycode"])
@keywords("屏幕截图")
def screenshot(test, device, **kwargs):
System(test, device).screenshot(kwargs["data"]["name"])
@keywords("亮屏")
def screen_on(test, device, **kwargs):
System(test, device).screen_on(kwargs["system"])
@keywords("息屏")
def screen_off(test, device, **kwargs):
System(test, device).screen_off(kwargs["system"])
@keywords("强制等待")
def sleep(test, device, **kwargs):
System(test, device).sleep(kwargs["data"]["second"])
@keywords("隐式等待")
def implicitly_wait(test, device, **kwargs):
System(test, device).implicitly_wait(kwargs["data"]["second"])
@keywords("自定义")
def custom(test, device, **kwargs):
System(test, device).custom(**kwargs)
try:
return function
except:
return None
def find_view_opt(operate_name: str):
function = None
def keywords(name):
def back(func):
if name == operate_name:
nonlocal function
function = func
return back
@keywords("单击")
def click(test, device, **kwargs):
View(test, device).click(kwargs["element"]["element"])
@keywords("双击")
def double_click(test, device, **kwargs):
View(test, device).double_click(kwargs["system"], kwargs["element"]["element"])
@keywords("长按")
def long_click(test, device, **kwargs):
View(test, device).long_click(kwargs["system"], kwargs["element"]["element"], kwargs["data"]["second"])
@keywords("坐标单击")
def click_coord(test, device, **kwargs):
View(test, device).click_coord(**kwargs["data"])
@keywords("坐标双击")
def double_click_coord(test, device, **kwargs):
View(test, device).double_click_coord(kwargs["system"], **kwargs["data"])
@keywords("坐标长按")
def long_click_coord(test, device, **kwargs):
View(test, device).long_click_coord(kwargs["system"], **kwargs["data"])
@keywords("坐标滑动")
def swipe_int(test, device, **kwargs):
View(test, device).swipe(kwargs["system"], **kwargs["data"])
@keywords("输入")
def input_text(test, device, **kwargs):
View(test, device).input_text(kwargs["element"]["element"], kwargs["data"]["text"])
@keywords("清空")
def input_text(test, device, **kwargs):
View(test, device).clear_text(kwargs["system"], kwargs["element"]["element"])
@keywords("滑动到元素出现")
def scroll_to_ele(test, device, **kwargs):
View(test, device).scroll_to_ele(kwargs["system"], kwargs["element"]["element"], kwargs["data"]["direction"])
@keywords("缩小")
def pinch_in(test, device, **kwargs):
View(test, device).pinch_in(kwargs["system"], kwargs["element"]["element"])
@keywords("放大")
def pinch_out(test, device, **kwargs):
View(test, device).pinch_out(kwargs["system"], kwargs["element"]["element"])
@keywords("等待元素出现")
def wait(test, device, **kwargs):
View(test, device).wait(kwargs["element"]["element"], kwargs["data"]["second"])
@keywords("等待元素消失")
def wait_gone(test, device, **kwargs):
View(test, device).wait_gone(kwargs["system"], kwargs["element"]["element"], kwargs["data"]["second"])
@keywords("拖动到元素")
def drag_to_ele(test, device, **kwargs):
View(test, device).drag_to_ele(kwargs["element"]["startElement"],kwargs["element"]["endElement"])
@keywords("拖动到坐标")
def drag_to_coord(test, device, **kwargs):
View(test, device).drag_to_coord(kwargs["element"]["element"], **kwargs["data"])
@keywords("坐标拖动")
def drag_coord(test, device, **kwargs):
View(test, device).drag_coord(**kwargs["data"])
@keywords("元素内滑动")
def swipe_ele(test, device, **kwargs):
View(test, device).swipe_ele(kwargs["element"]["element"], kwargs["data"]["direction"])
@keywords("等待弹框出现")
def alert_wait(test, device, **kwargs):
View(test, device).alert_wait(kwargs["data"]["second"])
@keywords("弹框确认")
def alert_accept(test, device, **kwargs):
View(test, device).alert_accept()
@keywords("弹框取消")
def alert_dismiss(test, device, **kwargs):
View(test, device).alert_dismiss()
@keywords("弹框点击")
def alert_click(test, device, **kwargs):
View(test, device).alert_click(kwargs["data"]["name"])
@keywords("自定义")
def custom(test, device, **kwargs):
View(test, device).custom(**kwargs)
try:
return function
except:
return None
def find_assertion_opt(operate_name: str):
function = None
def keywords(name):
def back(func):
if name == operate_name:
nonlocal function
function = func
return back
@keywords("断言元素存在")
def assert_ele_exists(test, device, **kwargs):
return Assertion(test, device).assert_ele_exists(kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("断言元素文本")
def assert_ele_text(test, device, **kwargs):
return Assertion(test, device).assert_ele_text(kwargs["system"], kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("断言元素属性")
def assert_ele_attribute(test, device, **kwargs):
return Assertion(test, device).assert_ele_attribute(kwargs["element"]["element"], kwargs["data"]["attribute"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("断言元素位置")
def assert_ele_center(test, device, **kwargs):
return Assertion(test, device).assert_ele_center(kwargs["system"], kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("断言元素X坐标")
def assert_ele_x(test, device, **kwargs):
return Assertion(test, device).assert_ele_x(kwargs["system"], kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("断言元素Y坐标")
def assert_ele_y(test, device, **kwargs):
return Assertion(test, device).assert_ele_y(kwargs["system"], kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("断言弹框存在")
def assert_alert_exists(test, device, **kwargs):
return Assertion(test, device).assert_alert_exists(kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("断言弹框文本")
def assert_alert_text(test, device, **kwargs):
return Assertion(test, device).assert_alert_text(kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("自定义")
def custom(test, device, **kwargs):
return Assertion(test, device).custom(**kwargs)
try:
return function
except:
return None
def find_relation_opt(operate_name: str):
function = None
def keywords(name):
def back(func):
if name == operate_name:
nonlocal function
function = func
return back
@keywords("提取屏幕尺寸")
def get_window_size(test, device, **kwargs):
Relation(test, device).get_window_size(kwargs["system"], kwargs["data"]["save_name"])
@keywords("提取屏幕宽度")
def get_window_width(test, device, **kwargs):
Relation(test, device).get_window_width(kwargs["system"], kwargs["data"]["save_name"])
@keywords("提取屏幕高度")
def get_window_height(test, device, **kwargs):
Relation(test, device).get_window_height(kwargs["system"], kwargs["data"]["save_name"])
@keywords("提取元素文本")
def get_ele_text(test, device, **kwargs):
Relation(test, device).get_ele_text(kwargs["system"], kwargs["element"]["element"], kwargs["data"]["save_name"])
@keywords("提取元素位置")
def get_ele_center(test, device, **kwargs):
Relation(test, device).get_ele_center(kwargs["system"], kwargs["element"]["element"], kwargs["data"]["save_name"])
@keywords("提取元素X坐标")
def get_ele_x(test, device, **kwargs):
Relation(test, device).get_ele_x(kwargs["system"], kwargs["element"]["element"], kwargs["data"]["save_name"])
@keywords("提取元素Y坐标")
def get_ele_y(test, device, **kwargs):
Relation(test, device).get_ele_y(kwargs["system"], kwargs["element"]["element"], kwargs["data"]["save_name"])
@keywords("提取弹框文本")
def get_alert_text(test, device, **kwargs):
Relation(test, device).get_alert_text(kwargs["data"]["save_name"])
@keywords("自定义")
def custom(test, device, **kwargs):
Relation(test, device).custom(**kwargs)
try:
return function
except:
return None
def find_condition_opt(operate_name: str):
function = None
def keywords(name):
def back(func):
if name == operate_name:
nonlocal function
function = func
return back
@keywords("判断元素存在")
def condition_ele_exists(test, device, **kwargs):
return Condition(test, device).condition_ele_exists(kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("判断元素文本")
def condition_ele_text(test, device, **kwargs):
return Condition(test, device).condition_ele_text(kwargs["system"], kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("判断元素属性")
def condition_ele_attribute(test, device, **kwargs):
return Condition(test, device).condition_ele_attribute(kwargs["element"]["element"], kwargs["data"]["attribute"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("判断元素位置")
def condition_ele_center(test, device, **kwargs):
return Condition(test, device).condition_ele_center(kwargs["system"], kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("判断元素X坐标")
def condition_ele_x(test, device, **kwargs):
return Condition(test, device).condition_ele_x(kwargs["system"], kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("判断元素Y坐标")
def condition_ele_y(test, device, **kwargs):
return Condition(test, device).condition_ele_y(kwargs["system"], kwargs["element"]["element"],
kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("判断弹框存在")
def condition_alert_exists(test, device, **kwargs):
return Condition(test, device).condition_alert_exists(kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("判断弹框文本")
def condition_alert_text(test, device, **kwargs):
return Condition(test, device).condition_alert_text(kwargs["data"]["assertion"], kwargs["data"]["expect"])
@keywords("自定义")
def custom(test, device, **kwargs):
return Condition(test, device).custom(**kwargs)
try:
return function
except:
return None
def find_scenario_opt(operate_name: str):
function = None
def keywords(name):
def back(func):
if name == operate_name:
nonlocal function
function = func
return back
@keywords("自定义")
def custom(test, device, **kwargs):
return Scenario(test, device).custom(**kwargs)
try:
return function
except:
return None
================================================
FILE: core/app/testcase.py
================================================
from core.template import Template
from core.app.collector import AppOperationCollector
from core.app.teststep import AppTestStep
from core.app.device import connect_device
from tools.utils.utils import get_case_message, handle_operation_data, handle_params_data
import re
class AppTestCase:
def __init__(self, test):
self.test = test
self.context = test.context
self.case_message = get_case_message(test.test_data)
self.id = self.case_message['caseId']
self.name = self.case_message['caseName']
setattr(test, 'test_case_name', self.case_message['caseName'])
setattr(test, 'test_case_desc', self.case_message['comment'])
self.functions = self.case_message['functions']
self.params = handle_params_data(self.case_message['params'])
test.common_params = self.params
self.device = self.before_execute()
self.template = Template(self.test, self.context, self.functions, self.params)
self.comp = re.compile(r"\{\{.*?\}\}")
def execute(self):
if self.case_message['optList'] is None:
self.after_execute()
raise RuntimeError("无法获取APP测试相关数据, 请重试!!!")
try:
self.loop_execute(self.case_message['optList'], [])
finally:
self.after_execute()
def loop_execute(self, opt_list, skip_opts, step_n=0):
while step_n < len(opt_list):
opt_content = opt_list[step_n]
# 定义收集器
collector = AppOperationCollector()
step = AppTestStep(self.test, self.device, self.context, collector)
# 定义事务
self.test.defineTrans(opt_content["operationId"], opt_content['operationTrans'],
self.get_opt_content(opt_content['operationElement']), opt_content['operationDesc'])
if step_n in skip_opts:
self.test.updateTransStatus(3)
self.test.debugLog('[{}]操作在条件控制之外不被执行'.format(opt_content['operationTrans']))
step_n += 1
continue
# 收集步骤信息
step.collector.collect(opt_content)
try:
if step.collector.opt_type == "looper":
looper_step_num = step.looper_controller(self, opt_list, step_n)
step_n += looper_step_num + 1
else:
# 渲染主体
self.render_content(step)
step.execute()
step.assert_controller()
skip_opts.extend(step.condition_controller(step_n))
step_n += 1
except Exception as e:
if not isinstance(e, AssertionError):
self.test.saveScreenShot(opt_content['operationTrans'], self.device.screenshot(format='raw'))
raise e
@staticmethod
def get_opt_content(elements):
content = ""
if elements is not None:
for key, element in elements.items():
content = "%s\n %s: %s" % (content, key, element["target"])
return content
def before_execute(self):
if self.case_message['deviceUrl'] is None:
raise Exception("执行设备不在线 本用例执行失败")
device = connect_device(self.case_message['deviceSystem'], f"http://{self.case_message['deviceUrl']}")
if self.case_message['deviceSystem'] == 'android':
device.healthcheck()
device.app_start(self.case_message['appId'], self.case_message['activity'])
return device
else:
device = device.session(self.case_message['appId'])
device._wda_url = f"http://{self.case_message['deviceUrl']}"
return device
def after_execute(self):
self.device.app_stop(self.case_message['appId'])
def render_looper(self, looper):
self.template.init(looper)
_looper = self.template.render()
for name, param in _looper.items():
if name != "target" or name != "expect": # 断言实际值不作数据处理
_looper[name] = handle_operation_data(param["type"], param["value"])
if "times" in _looper:
try:
times = int(_looper["times"])
except:
times = 1
_looper["times"] = times
return _looper
def render_content(self, step):
if step.collector.opt_element is not None:
for name, expression in step.collector.opt_element.items():
if self.comp.search(str(expression)) is not None:
self.template.init(expression)
expression = self.template.render()
step.collector.opt_element[name] = expression
if step.collector.opt_data is not None:
data = {}
for name, param in step.collector.opt_data.items():
param_value = param["value"]
if isinstance(param_value, str) and self.comp.search(param_value) is not None:
self.template.init(param_value)
param_value = self.template.render()
data[name] = handle_operation_data(param["type"], param_value)
step.collector.opt_data = data
================================================
FILE: core/app/teststep.py
================================================
import sys
from datetime import datetime
from core.app.find_opt import *
from core.assertion import LMAssert
class AppTestStep:
def __init__(self, test, device, context, collector):
self.test = test
self.device = device
self.context = context
self.collector = collector
self.result = None
def execute(self):
try:
self.test.debugLog('APP操作[{}]开始'.format(self.collector.opt_name))
opt_type = self.collector.opt_type
if opt_type == "system":
func = find_system_opt(self.collector.opt_name)
elif opt_type == "view":
func = find_view_opt(self.collector.opt_name)
elif opt_type == "condition":
func = find_condition_opt(self.collector.opt_name)
elif opt_type == "assertion":
func = find_assertion_opt(self.collector.opt_name)
elif opt_type == "relation":
func = find_relation_opt(self.collector.opt_name)
else:
func = find_scenario_opt(self.collector.opt_name)
if func is None:
raise NotExistedAppOperation("未定义操作")
opt_content = {
"system": self.collector.opt_system,
"trans": self.collector.opt_trans,
"code": self.collector.opt_code,
"element": self.collector.opt_element,
"data": self.collector.opt_data
}
self.result = func(self.test, self.device, **opt_content)
self.log_show()
finally:
self.test.debugLog('APP操作[{}]结束'.format(self.collector.opt_name))
def looper_controller(self, case, opt_list, step_n):
"""循环控制器"""
if self.collector.opt_trans == "While循环":
loop_start_time = datetime.now()
timeout = int(self.collector.opt_data["timeout"]["value"])
index_name = self.collector.opt_data["indexName"]["value"]
steps = int(self.collector.opt_data["steps"]["value"])
index = 0
while timeout == 0 or (datetime.now() - loop_start_time).seconds * 1000 < timeout:
# timeout为0时可能会死循环 慎重选择
self.context[index_name] = index # 给循环索引赋值第几次循环 母循环和子循环的索引名不应一样
_looper = case.render_looper(self.collector.opt_data) # 渲染循环控制控制器 每次循环都需要渲染
index += 1
result, _ = LMAssert(_looper['assertion'], _looper['target'], _looper['expect']).compare()
if not result:
break
_opt_list = opt_list[step_n+1: (step_n + _looper["steps"]+1)] # 循环操作本身不参与循环 不然死循环
case.loop_execute(_opt_list, [])
return steps
else:
_looper = case.render_looper(self.collector.opt_data) # 渲染循环控制控制器 for只需渲染一次
for index in range(_looper["times"]): # 本次循环次数
self.context[_looper["indexName"]] = index # 给循环索引赋值第几次循环 母循环和子循环的索引名不应一样
_opt_list = opt_list[step_n+1: (step_n + _looper["steps"]+1)]
case.loop_execute(_opt_list, [])
return _looper["steps"]
def assert_controller(self):
if self.collector.opt_type == "assertion":
if self.result[0]:
self.test.debugLog('[{}]断言成功: {}'.format(self.collector.opt_trans,
self.result[1]))
else:
self.test.errorLog('[{}]断言失败: {}'.format(self.collector.opt_trans,
self.result[1]))
self.test.saveScreenShot(self.collector.opt_trans, self.device.screenshot(format='raw'))
if "continue" in self.collector.opt_data and self.collector.opt_data["continue"] is True:
try:
raise AssertionError(self.result[1])
except AssertionError:
error_info = sys.exc_info()
self.test.recordFailStatus(error_info)
else:
raise AssertionError(self.result[1])
def condition_controller(self, current):
if self.collector.opt_type == "condition":
offset_true = self.collector.opt_data["true"]
if not isinstance(offset_true, int):
offset_true = 0
offset_false = self.collector.opt_data["false"]
if not isinstance(offset_false, int):
offset_false = 0
if self.result[0]:
self.test.debugLog('[{}]判断成功, 执行成功分支: {}'.format(self.collector.opt_name,
self.result[1]))
return [current + i for i in range(offset_true + 1, offset_true + offset_false + 1)]
else:
self.test.errorLog('[{}]判断失败, 执行失败分支: {}'.format(self.collector.opt_name,
self.result[1]))
return [current + i for i in range(1, offset_true + 1)]
return []
def log_show(self):
msg = ""
if self.collector.opt_element is not None:
for k, v in self.collector.opt_element.items():
msg += '元素定位: {}: {}<br>'.format(k, v)
if self.collector.opt_data is not None:
data_log = '{'
for k, v in self.collector.opt_data.items():
class_name = type(v).__name__
data_log += "{}: {}, ".format(k, v)
if len(data_log) > 1:
data_log = data_log[:-2]
data_log += '}'
msg += '操作数据: {}'.format(data_log)
if msg != "":
msg = '操作信息: <br>' + msg
self.test.debugLog(msg)
class NotExistedAppOperation(Exception):
"""未定义的操作"""
================================================
FILE: core/assertion.py
================================================
# -*- coding: utf-8 -*-
import re
import ast
from assertpy import assertpy
class LMAssert:
"""断言"""
def __init__(self, position, actual_result, expected_result):
self.comparator = position
self.actual_result = actual_result
self.expected_result = expected_result
def compare(self):
try:
if self.comparator in ["equal", "equals", "相等", "字符相等"]: # 等于
assFailMsg = '实际值({})与预期值({}) 字符相等,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(str(self.actual_result)).is_equal_to(self.expected_result)
elif self.comparator in ["equalsList", "数组相等"]: # 列表相同,包括列表顺序也相同
assFailMsg = '实际值({})与预期值({}) 数组相等,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2list(self.actual_result)).is_equal_to(LMAssert.str2list(self.expected_result))
elif self.comparator in ["equalsDict", "对象相等"]: # 字典相同
assFailMsg = '实际值({})与预期值({}) 对象相等,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2dict(self.actual_result)).is_equal_to(LMAssert.str2dict(self.expected_result))
elif self.comparator in ["equalsNumber", "数字相等", "数值相等"]: # 数字等于
assFailMsg = '实际值({})与预期值({}) 数值相等,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_equal_to(LMAssert.str2num(self.expected_result))
elif self.comparator in ["equalIgnoreCase", "相等(忽略大小写)"]: # 忽略大小写等于
assFailMsg = '实际值({})与预期值({}) 相等(忽略大小写),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(str(self.actual_result)).is_equal_to_ignoring_case(self.expected_result)
elif self.comparator in ["notEqual", "does not equal", "不等于"]: # 不等于
assFailMsg = '实际值({}) 不等于 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_not_equal_to(self.expected_result)
elif self.comparator in ["contains", "包含"]: # 字符串包含该字符
assFailMsg = '实际值({}) 包含 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.to_str(self.actual_result)).contains((self.expected_result))
elif self.comparator in ["notContains", "does no contains", "不包含"]: # 字符串不包含该字符
assFailMsg = '实际值({}) 不包含 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).does_not_contain(*LMAssert.str2list(self.expected_result))
elif self.comparator in ["containsOnly", "仅包含"]: # 字符串仅包含该字符
assFailMsg = '实际值({}) 仅包含 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).contains_only(*LMAssert.str2list(self.expected_result))
elif self.comparator in ["isNone", "none/null"]: # 为none或null
assFailMsg = '实际值({}) 为none或null,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2none(self.actual_result)).is_none()
elif self.comparator in ["notEmpty", "is not empty", "不为空"]: # 不为空
assFailMsg = '实际值({}) 不为空,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_not_empty()
elif self.comparator in ["empty", "is empty", "为空"]: # 为空
assFailMsg = '实际值({}) 为空,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_empty()
elif self.comparator in ["isTrue", "true"]: # 是true
assFailMsg = '实际值({}) 是true,条件为否:'.format(self.actual_result, self.expected_result)
res = False if LMAssert.str2bool(self.actual_result) is None else LMAssert.str2bool(self.actual_result)
assertpy.assert_that(res).is_true()
elif self.comparator in ["isFalse", "false"]: # 是false
assFailMsg = '实际值({}) 是false,条件为否:'.format(self.actual_result, self.expected_result)
res = True if LMAssert.str2bool(self.actual_result) is None else LMAssert.str2bool(self.actual_result)
assertpy.assert_that(res).is_false()
elif self.comparator in ["isStrType", "字符串"]: # 是str的类型
assFailMsg = '实际值({}) 是字符串,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_type_of(str)
elif self.comparator in ["isIntType", "整数"]: # 是int的类型
assFailMsg = '实际值({}) 是整数,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_type_of(int)
elif self.comparator in ["isFloatType", "浮点数"]: # 是浮点的类型
assFailMsg = '实际值({}) 是浮点数,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_type_of(float)
elif self.comparator in ["isInt", "is a number", "仅含数字"]: # 字符串中仅含有数字
assFailMsg = '实际值({}) 仅含数字,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_digit()
elif self.comparator in ["isLetter", "仅含字母"]: # 字符串中仅含有字母
assFailMsg = '实际值({}) 仅含字母,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_alpha()
elif self.comparator in ["isLower", "小写"]: # 是小写的
assFailMsg = '实际值({}) 是小写的,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_lower()
elif self.comparator in ["isUpper", "大写"]: # 是大写的
assFailMsg = '实际值({}) 是大写的,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_upper()
elif self.comparator in ["startWith", "开头是"]: # 字符串以该字符开始
assFailMsg = '实际值({}) 开头是 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).starts_with(self.expected_result)
elif self.comparator in ["endWith", "结尾是"]: # 字符串以该字符结束
assFailMsg = '实际值({}) 结尾是 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).ends_with(self.expected_result)
elif self.comparator in ["isIn", "has item", "包含对象", "被包含"]: # 在这几个字符串中
assFailMsg = '实际值({}) 被包含在 预期值({}) 列表中,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_in(*LMAssert.str2list(self.expected_result))
elif self.comparator in ["isNotIn", "不被包含"]: # 不在这几个字符串中
assFailMsg = '实际值({}) 不被包含在 预期值({}) 列表中,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_not_in(*LMAssert.str2list(self.expected_result))
elif self.comparator in ["isNotZero", "非0"]: # 不是0
assFailMsg = '实际值({}) 不是0,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_not_zero()
elif self.comparator in ["isZero", "为0"]: # 是0
assFailMsg = '实际值({}) 是0,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_zero()
elif self.comparator in ["isPositive", "正数"]: # 是正数
assFailMsg = '实际值({}) 是正数,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_positive()
elif self.comparator in ["isNegative", "负数"]: # 是负数
assFailMsg = '实际值({}) 是负数,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(self.actual_result).is_negative()
elif self.comparator in ["isGreaterThan", " 大于"]: # 大于
assFailMsg = '实际值({}) 大于 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_greater_than(LMAssert.str2num(self.expected_result))
elif self.comparator in ["isGreaterThanOrEqualTo", "greater than or equal", ">=", " 大于等于"]: # 大于等于
assFailMsg = '实际值({}) 大于等于 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_greater_than_or_equal_to(LMAssert.str2num(self.expected_result))
elif self.comparator in ["isLessThan", " 小于"]: # 小于
assFailMsg = '实际值({}) 小于 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_less_than(LMAssert.str2num(self.expected_result))
elif self.comparator in ["isLessThanOrEqualTo", "less than or equal", "<=", " 小于等于"]: # 小于等于
assFailMsg = '实际值({}) 小于等于 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_less_than_or_equal_to(LMAssert.str2num(self.expected_result))
elif self.comparator in ["isBetween", " 在...之间"]: # 在...之间
assFailMsg = '实际值({}) 在 预期值({}) 之间,条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_between(*LMAssert.str2list(self.expected_result))
elif self.comparator in ["isCloseTo", " 接近于"]: # 接近于
assFailMsg = '实际值({}) 接近于 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_close_to(*LMAssert.str2list(self.expected_result))
elif self.comparator in ["listLenEqual","列表长度相等"]: # 列表长度相等
assFailMsg = '实际值({}) 列表长度相等 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.list_len(self.actual_result)).is_equal_to(LMAssert.str2num(self.expected_result))
elif self.comparator in ["listLenGreaterThan","列表长度大于"]: # 列表长度大于
assFailMsg = '实际值({}) 列表长度大于 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.list_len(self.actual_result)).is_greater_than(LMAssert.str2num(self.expected_result))
elif self.comparator in ["listLenLessThan","列表长度小于"]: # 列表长度小于
assFailMsg = '实际值({}) 列表长度小于 预期值({}),条件为否:'.format(self.actual_result, self.expected_result)
assertpy.assert_that(LMAssert.list_len(self.actual_result)).is_less_than_or_equal_to(LMAssert.str2num(self.expected_result))
else:
raise AssertionTypeNotExist('没有{}该断言类型'.format(self.comparator))
return True, 'success'
except AssertionError as e:
ex = str(e).replace("Expected <", "Expected (").replace(">, ", "), ").replace(" <", " (").replace("> ", ") ")
return False, assFailMsg + ex
@staticmethod
def str2none(value):
if str(value).lower() == "none" or str(value).lower() == "null":
return None
else:
return value
@staticmethod
def str2bool(value):
if str(value).lower() == "true":
return True
elif str(value).lower() == "false":
return False
else:
return None
@staticmethod
def str2num(value):
if type(value) == int or type(value) == float:
return value
if value is None or len(value) == 0:
return None
elif re.fullmatch(r'-?[0-9]*\.?[0-9]*', value) is not None:
if '.' in value:
return float(value)
else:
return int(value)
else:
return value
@staticmethod
def str2list(value):
if type(value) == list or type(value) == int or type(value) == float:
return value
if value is None or len(value) == 0:
return None
value_list = []
if value.startswith('[') and value.endswith(']'):
for item in value[1:-1].split(','):
item_strip = item.strip()
if re.fullmatch(r'-?[0-9]*\.?[0-9]*', item_strip) is not None:
if '.' in item_strip:
value_list.append(float(item_strip))
else:
value_list.append(int(item_strip))
else: # 字符串
value_list.append(item_strip[1:-1])
return value_list
else:
return value
@staticmethod
def str2dict(value):
if type(value) == dict or type(value) == int or type(value) == float:
return value
if value is None or len(value) == 0:
return None
value_dict={}
if value.startswith('{') and value.endswith('}'):
for item in value[1:-1].split(','):
value_dict = ast.literal_eval(value)
return value_dict
else:
return value
@staticmethod
def to_str(value):
if type(value) == int or type(value) == float:
return value
if value is None or len(value) == 0:
return ""
if type(value) == str:
return value
else:
return str(value)
@staticmethod
def list_len(value):
value2list=LMAssert.str2list(value)
if type(value2list) != list:
raise AssertionTypeNotExist('传入实际值({}) 不是列表格式'.format(value))
else:
return len(value2list)
class AssertionTypeNotExist(Exception):
"""断言类型错误"""
================================================
FILE: core/template.py
================================================
from functools import reduce
from hashlib import md5
from jsonpath import jsonpath
from jsonpath_ng.parser import JsonPathParser
from tools.funclib import get_func_lib
import json
import re
import time
from tools.utils.utils import extract_by_jsonpath, quotation_marks
class Template:
def __init__(self, test, context, functions, params, variable_start_string='{{', variable_end_string='}}', function_prefix='@', param_prefix='$'):
self.test = test
self.param_prefix = param_prefix
self.data = None
self.context = context # 关联参数
self.params = params # 公共参数
self.variable_start_string = variable_start_string
self.variable_end_string = variable_end_string
self.function_prefix = function_prefix
self.param_prefix = param_prefix
self.stack = list()
# 动态存储接口的请求信息 以便渲染
self.request_url = None
self.request_path = None
self.request_headers = None
self.request_query = None
self.request_body = None
self.func_lib = get_func_lib(test, functions, self.context, self.params)
self.bytes_map = dict()
self.parser = JsonPathParser()
def init(self, data):
self.data = json.dumps(data, ensure_ascii=False)
self.stack.clear()
self.bytes_map.clear()
def set_help_data(self, url, path: str, headers: dict, query: dict, body: dict):
self.request_url = url
self.request_path = path
self.request_headers = headers
self.request_query = query
self.request_body = body
def render(self):
start_stack = list()
start_length = len(self.variable_start_string)
end_length = len(self.variable_end_string)
top = 0
flag = False
for cur in range(len(self.data)):
self.stack.append(self.data[cur])
top += 1
if flag:
self.stack.pop()
top -= 1
flag = False
continue
if reduce(lambda x, y: x + y, self.stack[-start_length:]) == self.variable_start_string:
start_stack.append(top - start_length)
if reduce(lambda x, y: x + y, self.stack[-end_length:]) == self.variable_end_string:
if len(start_stack) == 0:
continue
recent = start_stack.pop()
tmp = ''
for _ in range(top - recent):
tmp += self.stack.pop()
top -= 1
if self.stack[-1] == '"' and self.data[cur + 1] == '"':
self.stack.pop()
top -= 1
flag = True
else:
flag = False
tmp = tmp[::-1]
key = tmp[start_length:-end_length].strip()
key, json_path = self.split_key(key)
try:
if key.startswith(self.function_prefix):
name_args = self.split_func(key, self.function_prefix)
value = self.func_lib(name_args[0], *name_args[1:])
elif key in self.context: # 优先从关联参数中取
if json_path is None:
value = self.context.get(key)
else:
value = extract_by_jsonpath(self.context.get(key), json_path)
elif key in self.params:
if json_path is None:
value = self.params.get(key)
else:
value = extract_by_jsonpath(self.params.get(key), json_path)
elif key.startswith(self.param_prefix) and key[1:] in self.params: # 兼容老版本
if json_path is None:
value = self.params.get(key[1:])
else:
value = extract_by_jsonpath(self.params.get(key[1:]), json_path)
else:
value = tmp
except:
value = tmp
print('不存在的公共参数、关联变量或内置函数: {}'.format(key), file=self.test.stdout_buffer)
if not flag and isinstance(value, str):
if '"' in value and value != tmp:
value = json.dumps(value)[1:-1]
final_value = value
elif isinstance(value, bytes):
final_value = self._bytes_save(value, flag)
elif isinstance(value, list):
final_value = list()
for list_item in value:
if isinstance(list_item, bytes):
final_value.append(self._bytes_save(list_item, False))
else:
final_value.append(list_item)
final_value = json.dumps(final_value)
else:
if value == tmp and isinstance(value, str):
final_value = '"'+value+'"'
else:
final_value = json.dumps(value)
for s in final_value:
self.stack.append(s)
top += 1
res = json.loads(reduce(lambda x, y: x + y, self.stack))
if len(self.bytes_map) > 0:
pattern = r'#\{(bytes_\w+_\d+?)\}'
if isinstance(res, str):
bytes_value = self._bytes_slove(res, pattern)
if bytes_value is not None:
res = bytes_value
elif isinstance(res, dict) or isinstance(res, list):
for i, j in zip(jsonpath(res, '$..'), jsonpath(res, '$..', result_type='PATH')):
if isinstance(i, str):
bytes_value = self._bytes_slove(i, pattern)
if bytes_value is not None:
expression = self.parser.parse(j)
expression.update(res, bytes_value)
return res
def _bytes_save(self, value, flag):
bytes_map_key = 'bytes_{}_{}'.format(md5(value).hexdigest(), int(time.time() * 1000000000))
self.bytes_map[bytes_map_key] = value
change_value = '#{%s}' % bytes_map_key
if flag:
final_value = json.dumps(change_value)
else:
final_value = change_value
return final_value
def _bytes_slove(self, s, pattern):
search_result = re.search(pattern, s)
if search_result is not None:
expr = search_result.group(1)
return self.bytes_map[expr]
def replace_param(self, param):
param = param.strip()
search_result = re.search(r'#\{(.*?)\}', param)
if search_result is not None:
expr = search_result.group(1).strip()
if expr.lower() == '_request_url':
return self.request_url
elif expr.lower() == '_request_path':
return self.request_path
elif expr.lower() == '_request_header':
return self.request_headers
elif expr.lower() == '_request_body':
return self.request_body
elif expr.lower() == '_request_query':
return self.request_query
elif expr.startswith('bytes_'):
return self.bytes_map[expr]
else:
# 支持从请求头和查询参数中取单个数据
if expr.lower().startswith("_request_header."):
data = self.request_headers
expr = '$.' + expr[16:]
elif expr.lower().startswith("_request_query."):
data = self.request_query
expr = '$.' + expr[15:]
else:
data = self.request_body
if expr.lower().startswith("_request_body."):
expr = '$.' + expr[14:]
elif not expr.startswith('$'):
expr = '$.' + expr
try:
return extract_by_jsonpath(data, expr)
except:
return param
else:
return param
def split_key(self, key: str):
if key.startswith(self.function_prefix):
return key, None
key_list = key.split(".")
key = key_list[0]
json_path = None
if len(key_list) > 1:
json_path = reduce(lambda x, y: x + '.' + y, key_list[1:])
if key.endswith(']') and '[' in key:
keys = key.split("[")
key = keys[0]
if json_path is None:
json_path = keys[-1][:-1]
else:
json_path = keys[-1][:-1] + "." + json_path
if json_path is not None:
json_path = "$." + json_path
return key, json_path
def split_func(self, statement: str, flag: 'str' = '@'):
pattern = flag + r'([_a-zA-Z][_a-zA-Z0-9]*)(\(.*?\))?'
m = re.match(pattern, statement)
result = list()
if m is not None:
name, _ = m.groups()
args = statement.replace(flag+name, "")
result.append(name)
if args is not None and args != '()':
argList = [str(_) for _ in map(self.replace_param, args[1:-1].split(','))]
argList_length = len(argList)
if not (argList_length == 1 and len(argList[0]) == 0):
if name not in self.func_lib.func_param:
for i in range(argList_length):
result.append(argList[i])
else:
type_list = self.func_lib.func_param[name]
j = 0
for i in range(len(type_list)):
if j >= argList_length:
break
if type_list[i] is str:
result.append(quotation_marks(argList[j]))
j += 1
elif type_list[i] is int:
result.append(int(argList[j]))
j += 1
elif type_list[i] is float:
result.append(float(argList[j]))
j += 1
elif type_list[i] is bool:
result.append(False if argList[j].lower() == 'false' else True)
j += 1
elif type_list[i] is dict:
j, r = self.concat(j, argList, '}')
result.append(r)
elif type_list[i] is list:
j, r = self.concat(j, argList, ']')
result.append(r)
elif type_list[i] is bytes:
result.append(argList[j])
j += 1
elif type_list[i] is None:
result.append(argList[j])
j += 1
else:
raise SplitFunctionError('函数{}第{}个参数类型错误: {}'.format(name, i + 1, type_list[i]))
return result
else:
raise SplitFunctionError('函数错误: {}'.format(statement))
@staticmethod
def concat(start: int, arg_list: list, terminal_char: str):
end = start
length = len(arg_list)
for i in range(start, length):
if terminal_char in arg_list[i]:
end = i
s = reduce(lambda x, y: x + ',' + y, arg_list[start:end + 1])
try:
return end + 1, eval(quotation_marks(s))
except:
try:
s = '"'+s+'"'
return end + 1, eval(json.loads(s))
except:
continue
else:
s = reduce(lambda x, y: x + ',' + y, arg_list[start:end + 1])
return end + 1, s
class SplitFunctionError(Exception):
"""函数处理错误"""
================================================
FILE: core/web/collector.py
================================================
from selenium.webdriver.common.by import By
locator = {
"ID": By.ID,
"XPATH": "xpath",
"LINK": "link text",
"PARTIAL": "partial link text",
"NAME": "name",
"TAG": "tag name",
"CLASS": "class name",
"CSS": "css selector"
}
class WebOperationCollector:
def __init__(self):
self.id = None
self.opt_type = None
self.opt_name = None
self.opt_trans = None
self.opt_element = None
self.opt_data = None
self.opt_code = None
@staticmethod
def __parse(ui_data: dict, name):
if name not in ui_data:
return None
return ui_data.get(name)
def collect_id(self, ui_data):
self.id = WebOperationCollector.__parse(ui_data, "operationId")
def collect_opt_type(self, ui_data):
self.opt_type = WebOperationCollector.__parse(ui_data, "operationType")
def collect_opt_name(self, ui_data):
self.opt_name = WebOperationCollector.__parse(ui_data, "operationName")
def collect_opt_trans(self, ui_data):
self.opt_trans = WebOperationCollector.__parse(ui_data, "operationTrans")
def collect_opt_code(self, ui_data):
self.opt_code = WebOperationCollector.__parse(ui_data, "operationCode")
def collect_opt_element(self, ui_data):
opt_element = WebOperationCollector.__parse(ui_data, "operationElement")
if opt_element is None or len(opt_element) == 0:
self.opt_element = None
else:
elements = {}
for name, element in opt_element.items():
elements[name] = (locator[element["by"]], element["expression"])
self.opt_element = elements
def collect_opt_data(self, ui_data):
opt_data = WebOperationCollector.__parse(ui_data, "operationData")
if opt_data is None or len(opt_data) == 0:
self.opt_data = None
else:
self.opt_data = opt_data
def collect(self, ui_data):
self.collect_id(ui_data)
self.collect_opt_type(ui_data)
self.collect_opt_name(ui_data)
self.collect_opt_trans(ui_data)
self.collect_opt_element(ui_data)
self.collect_opt_data(ui_data)
self.collect_opt_code(ui_data)
================================================
FILE: core/web/driver/__init__.py
================================================
from selenium.common.exceptions import NoSuchElementException
class Operation(object):
def __init__(self, test, driver):
self.driver = driver
self.test = test
self.print = print
def find_element(self, ele):
"""查找单个元素"""
try:
element = self.driver.find_element(*tuple(ele))
self.test.debugLog("成功定位元素 'By: %s Expression: %s'" % ele)
return element
except Exception as e:
self.test.errorLog("无法定位元素 'By: %s Expression: %s'" % ele)
raise e
def find_elements(self, ele):
"""查找批量元素"""
try:
elements = self.driver.find_elements(*tuple(ele))
if len(elements) > 0:
self.test.debugLog("成功定位元素 'By: %s Expression: %s'" % ele)
return elements
else:
self.test.errorLog("无法定位元素 'By: %s Expression: %s'" % ele)
raise NoSuchElementException("Failed to find elements 'By: %s Expression: %s'" % ele)
except Exception as e:
self.test.errorLog("无法定位元素 'By: %s Expression: %s'" % ele)
raise e
================================================
FILE: core/web/driver/assertionOpt.py
================================================
import sys
from selenium.common.exceptions import NoSuchElementException
from core.assertion import LMAssert
from core.web.driver import Operation
class Assertion(Operation):
"""断言类操作"""
def assert_page_title(self, assertion, expect):
"""断言页面标题"""
try:
actual = self.driver.title
self.test.debugLog("成功获取title:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取title")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_page_url(self, assertion, expect):
"""断言页面url"""
try:
actual = self.driver.current_url
self.test.debugLog("成功获取url:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取url")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_page_source(self, assertion, expect):
"""断言页面源码"""
try:
actual = self.driver.page_source
self.test.debugLog("成功获取page source: 源码过长不予展示")
except Exception as e:
self.test.errorLog("无法获取page source")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_text(self, element, assertion, expect):
"""断言元素文本"""
try:
actual = self.find_element(element).text
self.test.debugLog("成功获取元素text:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素text")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_tag(self, element, assertion, expect):
"""断言元素tag"""
try:
actual = self.find_element(element).tag_name
self.test.debugLog("成功获取元素tag name:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素tag name")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_size(self, element, assertion, expect):
"""断言元素尺寸"""
try:
actual = self.find_element(element).size
self.test.debugLog("成功获取元素size:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素size")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_height(self, element, assertion, expect):
"""断言元素高度"""
try:
actual = self.find_element(element).size.get("height")
self.test.debugLog("成功获取元素height:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素height")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_width(self, element, assertion, expect):
"""断言元素宽度"""
try:
actual = self.find_element(element).size.get("width")
self.test.debugLog("成功获取元素width:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素width")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_location(self, element, assertion, expect):
"""断言元素位置"""
try:
actual = self.find_element(element).location
self.test.debugLog("成功获取元素location:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素location")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_x(self, element, assertion, expect):
"""断言元素X坐标"""
try:
actual = self.find_element(element).location.get("x")
self.test.debugLog("成功获取元素location x:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素location x")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_y(self, element, assertion, expect):
"""断言元素Y坐标"""
try:
actual = self.find_element(element).location.get("y")
self.test.debugLog("成功获取元素location y:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素location y")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_attribute(self, element, name, assertion, expect):
"""断言元素属性"""
try:
actual = self.find_element(element).get_attribute(name)
self.test.debugLog("成功获取元素attribute:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素attribute")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_selected(self, element, assertion, expect):
"""断言元素是否选中"""
try:
actual = self.find_element(element).is_selected()
self.test.debugLog("成功获取元素selected:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素selected")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_enabled(self, element, assertion, expect):
"""断言元素是否启用"""
try:
actual = self.find_element(element).is_enabled()
self.test.debugLog("成功获取元素enabled:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素enabled")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_displayed(self, element, assertion, expect):
"""断言元素是否显示"""
try:
actual = self.find_element(element).is_displayed()
self.test.debugLog("成功获取元素displayed:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素displayed")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_css(self, element, name, assertion, expect):
"""断言元素css样式"""
try:
actual = self.find_element(element).value_of_css_property(name)
self.test.debugLog("成功获取元素css %s:%s" % (name, str(actual)))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素css %s" % name)
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_ele_existed(self, element, assertion, expect):
"""断言元素是否存在"""
try:
try:
self.find_elements(element)
actual = True
except NoSuchElementException:
actual = False
self.test.debugLog("成功获取元素existed:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素existed")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_window_position(self, assertion, expect):
"""断言窗口位置"""
try:
actual = self.driver.get_window_position()
self.test.debugLog("成功获取窗口position:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口position")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_window_x(self, assertion, expect):
"""断言窗口X坐标"""
try:
actual = self.driver.get_window_position().get("x")
self.test.debugLog("成功获取窗口position x:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口position x")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_window_y(self, assertion, expect):
"""断言窗口Y坐标"""
try:
actual = self.driver.get_window_position().get("y")
self.test.debugLog("成功获取窗口position y:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口position y")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_window_size(self, assertion, expect):
"""断言窗口大小"""
try:
actual = self.driver.get_window_size()
self.test.debugLog("成功获取窗口size:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口size")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_window_width(self, assertion, expect):
"""断言窗口宽度"""
try:
actual = self.driver.get_window_size().get("width")
self.test.debugLog("成功获取窗口width:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口width")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_window_height(self, assertion, expect):
"""断言窗口高度"""
try:
actual = self.driver.get_window_size().get("height")
self.test.debugLog("成功获取窗口height:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口height")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_cookies(self, assertion, expect):
"""断言cookies"""
try:
actual = self.driver.get_cookies()
self.test.debugLog("成功获取cookies:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取cookies")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def assert_cookie(self, name, assertion, expect):
"""断言cookie"""
try:
actual = self.driver.get_cookie(name)
self.test.debugLog("成功获取cookie %s:%s" % (name, str(actual)))
except Exception as e:
self.test.errorLog("无法获取cookie %s" % name)
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["driver"] = self.driver
names["test"] = self.test
try:
"""断言操作需要返回被断言的值 以sys_return(value)返回"""
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_return(res):
names["_exec_result"] = res
def sys_get(name):
if name in names["test"].context:
return names["test"].context[name]
elif name in names["test"].common_params:
return names["test"].common_params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
def sys_put(name, val, ps=False):
if ps:
names["test"].common_params[name] = val
else:
names["test"].context[name] = val
exec(code)
self.test.debugLog("成功执行 %s" % kwargs["trans"])
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法执行 %s" % kwargs["trans"])
raise e
else:
result, msg = LMAssert(kwargs["data"]["assertion"], names["_exec_result"], kwargs["data"]["expect"]).compare()
return result, msg
================================================
FILE: core/web/driver/browserOpt.py
================================================
import sys
from selenium.common.exceptions import NoSuchElementException
from core.web.driver import Operation
from datetime import datetime
from time import sleep
from tools.utils.utils import url_join
class Browser(Operation):
"""浏览器类操作"""
def max_window(self):
"""最大化窗口"""
try:
self.driver.maximize_window()
self.test.debugLog("成功执行maximize window")
except Exception as e:
self.test.errorLog("无法执行maximize window")
raise e
def min_window(self):
"""最小化窗口"""
try:
self.driver.minimize_window()
self.test.debugLog("成功执行minimize window")
except Exception as e:
self.test.errorLog("无法执行minimize window")
raise e
def full_window(self):
"""全屏窗口"""
try:
self.driver.fullscreen_window()
self.test.debugLog("成功执行full screen window")
except Exception as e:
self.test.errorLog("无法执行full screen window")
raise e
def set_position_window(self, x, y):
"""设置窗口位置"""
"""0,0是左上角"""
try:
self.driver.set_window_position(x, y)
self.test.debugLog("成功执行set window position")
except Exception as e:
self.test.errorLog("无法执行set window position")
raise e
def set_size_window(self, width, height):
"""设置窗口大小"""
try:
self.driver.set_window_size(width, height)
self.test.debugLog("成功执行set window size")
except Exception as e:
self.test.errorLog("无法执行set window size")
raise e
def switch_to_window(self, window):
"""切换窗口"""
try:
self.driver.switch_to.window(window)
self.test.debugLog("成功执行switch window")
except Exception as e:
self.test.errorLog("无法执行switch window")
raise e
def close_window(self):
"""关闭窗口"""
try:
self.driver.close()
self.test.debugLog("成功执行close window")
except Exception as e:
self.test.errorLog("无法执行close window")
raise e
def save_screenshot(self, name):
"""屏幕截图"""
try:
screenshot = self.driver.get_screenshot_as_png()
self.test.saveScreenShot(name, screenshot)
self.test.debugLog("成功执行screen shot")
except Exception as e:
self.test.errorLog("无法执行screen shot")
raise e
def click_to_new_window(self, element):
"""单击跳转新窗口"""
try:
current = self.driver.window_handles
# 点击打开新窗口
self.find_element(element).click()
# 等待新窗口出现
current_time = datetime.now()
while (datetime.now()-current_time).seconds < 60:
if len(self.driver.window_handles) > len(current):
for window_handle in self.driver.window_handles:
if window_handle not in current:
self.driver.switch_to.window(window_handle)
self.test.debugLog("成功执行click and switch to new window")
return
else:
sleep(2)
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法执行click and switch to new window")
raise e
def back_and_close_window(self, window):
"""返回并关闭当前窗口"""
try:
self.driver.close()
self.driver.switch_to.window(window)
self.test.debugLog("成功执行back and close window")
except Exception as e:
self.test.errorLog("无法执行back and close window")
raise e
def open_url(self, domain, path):
"""打开网页"""
try:
url = url_join(domain, path)
self.driver.get(url)
self.driver.implicitly_wait(2)
self.test.debugLog("成功打开 '%s'" % url_join(domain, path))
except Exception as e:
self.test.errorLog("无法打开 '%s'" % url_join(domain, path))
raise e
def refresh(self):
"""刷新页面"""
try:
self.driver.refresh()
self.test.debugLog("成功执行refresh")
except Exception as e:
self.test.errorLog("无法执行refresh")
raise e
def back(self):
"""页面后退"""
try:
self.driver.back()
self.test.debugLog("成功执行back")
except Exception as e:
self.test.errorLog("无法执行back")
raise e
def forward(self):
"""页面前进"""
try:
self.driver.forward()
self.test.debugLog("成功执行forward")
except Exception as e:
self.test.errorLog("无法执行forward")
raise e
def add_cookie(self, name, value):
"""添加cookie"""
try:
self.driver.add_cookie({'name': name, 'value': value})
self.test.debugLog("成功执行add cookie: %s:%s" % (name, value))
except Exception as e:
self.test.errorLog("无法执行add cookie: %s:%s" % (name, value))
raise e
def delete_cookie(self, name):
"""删除cookie"""
try:
self.driver.delete_cookie(name)
self.test.debugLog("成功执行delete cookie:%s" % name)
except Exception as e:
self.test.errorLog("无法执行delete cookie:%s" % name)
raise e
def delete_cookies(self):
"""删除cookies"""
try:
self.driver.delete_all_cookies()
self.test.debugLog("成功执行delete cookies")
except Exception as e:
self.test.errorLog("无法执行delete cookies")
raise e
def execute_script(self, script, arg:tuple):
"""执行脚本"""
try:
self.driver.execute_script(script, *arg)
self.test.debugLog("成功执行execute script:%s" % script)
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法执行execute script:%s" % script)
raise e
def execute_async_script(self, script, arg:tuple):
"""执行异步脚本"""
try:
self.driver.execute_async_script(script, *arg)
self.test.debugLog("成功执行execute async script:%s" % script)
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法执行execute async script:%s" % script)
raise e
def sleep(self, second):
"""强制等待"""
try:
sleep(second)
self.test.debugLog("成功执行sleep %ds" % second)
except Exception as e:
self.test.errorLog("无法执行sleep %ds" % second)
raise e
def implicitly_wait(self, second):
"""隐式等待"""
try:
self.driver.implicitly_wait(second)
self.test.debugLog("成功执行implicitly wait %ds" % second)
except Exception as e:
self.test.errorLog("无法执行implicitly wait %ds" % second)
raise e
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["driver"] = self.driver
names["test"] = self.test
try:
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_get(name):
if name in names["test"].context:
return names["test"].context[name]
elif name in names["test"].common_params:
return names["test"].common_params[name]
else:
raise KeyError("不存在的公共参数或关联变量: {}".format(name))
def sys_put(name, val, ps=False):
if ps:
names["test"].common_params[name] = val
else:
names["test"].context[name] = val
exec(code)
self.test.debugLog("成功执行 %s" % kwargs["trans"])
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法执行 %s" % kwargs["trans"])
raise e
================================================
FILE: core/web/driver/conditionOpt.py
================================================
import sys
from selenium.common.exceptions import NoSuchElementException
from core.assertion import LMAssert
from core.web.driver import Operation
class Condition(Operation):
"""条件类操作"""
def condition_page_title(self, assertion, expect):
"""判断页面标题"""
try:
actual = self.driver.title
self.test.debugLog("成功获取title:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取title")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_page_url(self, assertion, expect):
"""判断页面url"""
try:
actual = self.driver.current_url
self.test.debugLog("成功获取url:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取url")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_page_source(self, assertion, expect):
"""判断页面源码"""
try:
actual = self.driver.page_source
self.test.debugLog("成功获取page source: : 源码过长不予展示")
except Exception as e:
self.test.errorLog("无法获取page source")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_text(self, element, assertion, expect):
"""判断元素文本"""
try:
actual = self.find_element(element).text
self.test.debugLog("成功获取元素text:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素text")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_tag(self, element, assertion, expect):
"""判断元素tag"""
try:
actual = self.find_element(element).tag_name
self.test.debugLog("成功获取元素tag name:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素tag name")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_size(self, element, assertion, expect):
"""判断元素尺寸"""
try:
actual = self.find_element(element).size
self.test.debugLog("成功获取元素size:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素size")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_height(self, element, assertion, expect):
"""判断元素高度"""
try:
actual = self.find_element(element).size.get("height")
self.test.debugLog("成功获取元素height:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素height")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_width(self, element, assertion, expect):
"""判断元素宽度"""
try:
actual = self.find_element(element).size.get("width")
self.test.debugLog("成功获取元素width:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素width")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_location(self, element, assertion, expect):
"""判断元素位置"""
try:
actual = self.find_element(element).location
self.test.debugLog("成功获取元素location:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素location")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_x(self, element, assertion, expect):
"""判断元素X坐标"""
try:
actual = self.find_element(element).location.get("x")
self.test.debugLog("成功获取元素location x:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素location x")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_y(self, element, assertion, expect):
"""判断元素Y坐标"""
try:
actual = self.find_element(element).location.get("y")
self.test.debugLog("成功获取元素location y:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素location y")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_attribute(self, element, name, assertion, expect):
"""判断元素属性"""
try:
actual = self.find_element(element).get_attribute(name)
self.test.debugLog("成功获取元素attribute:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素attribute")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_selected(self, element, assertion, expect):
"""判断元素是否选中"""
try:
actual = self.find_element(element).is_selected()
self.test.debugLog("成功获取元素selected:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素selected")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_enabled(self, element, assertion, expect):
"""判断元素是否启用"""
try:
actual = self.find_element(element).is_enabled()
self.test.debugLog("成功获取元素enabled:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素enabled")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_displayed(self, element, assertion, expect):
"""判断元素是否显示"""
try:
actual = self.find_element(element).is_displayed()
self.test.debugLog("成功获取元素displayed:%s" % str(actual))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素displayed")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_css(self, element, name, assertion, expect):
"""判断元素css样式"""
try:
actual = self.find_element(element).value_of_css_property(name)
self.test.debugLog("成功获取元素css %s:%s" % (name, str(actual)))
except NoSuchElementException as e:
raise e
except Exception as e:
self.test.errorLog("无法获取元素css %s" % name)
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_ele_existed(self, element, assertion, expect):
"""判断元素是否存在"""
try:
try:
self.find_elements(element)
actual = True
except NoSuchElementException:
actual = False
self.test.debugLog("成功获取元素existed:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取元素existed")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_window_position(self, assertion, expect):
"""判断窗口位置"""
try:
actual = self.driver.get_window_position()
self.test.debugLog("成功获取窗口position:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口position")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_window_x(self, assertion, expect):
"""判断窗口X坐标"""
try:
actual = self.driver.get_window_position().get("x")
self.test.debugLog("成功获取窗口position x:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口position x")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_window_y(self, assertion, expect):
"""判断窗口Y坐标"""
try:
actual = self.driver.get_window_position().get("y")
self.test.debugLog("成功获取窗口position y:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口position y")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_window_size(self, assertion, expect):
"""判断窗口大小"""
try:
actual = self.driver.get_window_size()
self.test.debugLog("成功获取窗口size:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口size")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_window_width(self, assertion, expect):
"""判断窗口宽度"""
try:
actual = self.driver.get_window_size().get("width")
self.test.debugLog("成功获取窗口width:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口width")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_window_height(self, assertion, expect):
"""判断窗口高度"""
try:
actual = self.driver.get_window_size().get("height")
self.test.debugLog("成功获取窗口height:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取窗口height")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_cookies(self, assertion, expect):
"""判断cookies"""
try:
actual = self.driver.get_cookies()
self.test.debugLog("成功获取cookies:%s" % str(actual))
except Exception as e:
self.test.errorLog("无法获取cookies")
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def condition_cookie(self, name, assertion, expect):
"""判断cookie"""
try:
actual = self.driver.get_cookie(name)
self.test.debugLog("成功获取cookie %s:%s" % (name, str(actual)))
except Exception as e:
self.test.errorLog("无法获取cookie %s" % name)
raise e
else:
result, msg = LMAssert(assertion, actual, expect).compare()
return result, msg
def custom(self, **kwargs):
"""自定义"""
code = kwargs["code"]
names = locals()
names["element"] = kwargs["element"]
names["data"] = kwargs["data"]
names["driver"] = self.driver
names["test"] = self.test
try:
"""条件操作需要返回被断言的值 以sys_return(value)返回"""
def print(*args, sep=' ', end='\n', file=None, flush=False):
if file is None or file in (sys.stdout, sys.stderr):
file = names["test"].stdout_buffer
self.print(*args, sep=sep, end=end, file=file, flush=flush)
def sys_return(res):
gitextract_htwkq7hi/
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── browser/
│ └── readme.md
├── config/
│ └── config.ini
├── core/
│ ├── api/
│ │ ├── collector.py
│ │ ├── testcase.py
│ │ └── teststep.py
│ ├── app/
│ │ ├── collector.py
│ │ ├── device/
│ │ │ ├── __init__.py
│ │ │ ├── assertionOpt.py
│ │ │ ├── conditionOpt.py
│ │ │ ├── relationOpt.py
│ │ │ ├── scenarioOpt.py
│ │ │ ├── systemOpt.py
│ │ │ └── viewOpt.py
│ │ ├── find_opt.py
│ │ ├── testcase.py
│ │ └── teststep.py
│ ├── assertion.py
│ ├── template.py
│ └── web/
│ ├── collector.py
│ ├── driver/
│ │ ├── __init__.py
│ │ ├── assertionOpt.py
│ │ ├── browserOpt.py
│ │ ├── conditionOpt.py
│ │ ├── pageOpt.py
│ │ ├── relationOpt.py
│ │ └── scenarioOpt.py
│ ├── find_opt.py
│ ├── testcase.py
│ └── teststep.py
├── lm/
│ ├── lm_api.py
│ ├── lm_case.py
│ ├── lm_config.py
│ ├── lm_log.py
│ ├── lm_report.py
│ ├── lm_result.py
│ ├── lm_run.py
│ ├── lm_setting.py
│ ├── lm_start.py
│ ├── lm_upload.py
│ └── lm_ws.py
├── requirements.txt
├── startup.py
└── tools/
├── funclib/
│ ├── __init__.py
│ ├── load_faker.py
│ ├── params_enum.py
│ └── provider/
│ └── lm_provider.py
└── utils/
├── sql.py
└── utils.py
SYMBOL INDEX (509 symbols across 43 files)
FILE: core/api/collector.py
class ApiRequestCollector (line 7) | class ApiRequestCollector:
method __init__ (line 9) | def __init__(self):
method collect_flag (line 24) | def collect_flag(self, api_data, arg_name):
method collect_other (line 32) | def collect_other(self, api_data, arg_name, func=lambda x: x):
method collect_context (line 38) | def collect_context(self, api_data, arg_name):
method collect_id (line 44) | def collect_id(self, api_data):
method collect_name (line 47) | def collect_name(self, api_data):
method collect_protocol (line 50) | def collect_protocol(self, api_data):
method collect_method (line 53) | def collect_method(self, api_data):
method collect_url (line 60) | def collect_url(self, api_data):
method collect_path (line 66) | def collect_path(self, api_data):
method collect_controller (line 82) | def collect_controller(self, api_data):
method collect_conditions (line 99) | def collect_conditions(self, api_data):
method collect_looper (line 103) | def collect_looper(self, api_data):
method collect_query (line 107) | def collect_query(self, api_data):
method collect_headers (line 113) | def collect_headers(self, api_data):
method collect_cookies (line 116) | def collect_cookies(self, api_data):
method collect_proxies (line 127) | def collect_proxies(self, api_data):
method collect_body (line 130) | def collect_body(self, api_data):
method collect_stream (line 154) | def collect_stream(self, api_data):
method collect_verify (line 163) | def collect_verify(self, api_data):
method collect_auth (line 172) | def collect_auth(self, api_data):
method collect_timeout (line 175) | def collect_timeout(self, api_data):
method collect_allow_redirects (line 181) | def collect_allow_redirects(self, api_data):
method collect_hooks (line 184) | def collect_hooks(self, api_data):
method collect_cert (line 187) | def collect_cert(self, api_data):
method collect_assertions (line 190) | def collect_assertions(self, api_data):
method collect_relations (line 193) | def collect_relations(self, api_data):
method collect (line 196) | def collect(self, api_data):
class UnDefinableMethodError (line 224) | class UnDefinableMethodError(Exception):
class UnDefinablePathError (line 228) | class UnDefinablePathError(Exception):
class NotExistedFieldError (line 232) | class NotExistedFieldError(Exception):
class NotExistedFileUploadType (line 236) | class NotExistedFileUploadType(Exception):
FILE: core/api/testcase.py
class ApiTestCase (line 12) | class ApiTestCase:
method __init__ (line 14) | def __init__(self, test):
method execute (line 29) | def execute(self):
method loop_execute (line 35) | def loop_execute(self, api_list, loop_id, step_n=0):
method render_looper (line 100) | def render_looper(self, looper):
method render_conditions (line 111) | def render_conditions(self, conditions):
method render_sql (line 115) | def render_sql(self, sql):
method render_content (line 119) | def render_content(self, step):
method render_json (line 167) | def render_json(self, step, data, name, pop_key=None):
FILE: core/api/teststep.py
class ApiTestStep (line 25) | class ApiTestStep:
method __init__ (line 27) | def __init__(self, test, session, collector, context, params):
method execute (line 42) | def execute(self):
method looper_controller (line 105) | def looper_controller(self, case, api_list, step_n):
method condition_controller (line 127) | def condition_controller(self, case):
method exec_script (line 140) | def exec_script(self, code):
method exec_sql (line 170) | def exec_sql(self, sql, case):
method save_response (line 191) | def save_response(self, res):
method extract_depend_params (line 205) | def extract_depend_params(self):
method check (line 233) | def check(self):
method pop_content_type (line 267) | def pop_content_type(self):
function dict2str (line 279) | def dict2str(data):
class RemoveParamError (line 286) | class RemoveParamError(Exception):
class AssertRelationError (line 290) | class AssertRelationError(Exception):
FILE: core/app/collector.py
class AppOperationCollector (line 4) | class AppOperationCollector:
method __init__ (line 6) | def __init__(self):
method __parse (line 17) | def __parse(ui_data: dict, name):
method collect_id (line 22) | def collect_id(self, ui_data):
method collect_opt_type (line 25) | def collect_opt_type(self, ui_data):
method collect_opt_system (line 28) | def collect_opt_system(self, ui_data):
method collect_opt_name (line 31) | def collect_opt_name(self, ui_data):
method collect_opt_trans (line 34) | def collect_opt_trans(self, ui_data):
method collect_opt_code (line 37) | def collect_opt_code(self, ui_data):
method collect_opt_element (line 40) | def collect_opt_element(self, ui_data):
method collect_opt_data (line 60) | def collect_opt_data(self, ui_data):
method collect (line 67) | def collect(self, ui_data):
FILE: core/app/device/__init__.py
class AndroidDriver (line 6) | class AndroidDriver(Device):
method __call__ (line 8) | def __call__(self, **kwargs):
method find_element (line 14) | def find_element(self, **kwargs):
class AppleDevice (line 21) | class AppleDevice(Client):
method session (line 23) | def session(self,
method find_element (line 32) | def find_element(self, **kwargs):
function connect_device (line 36) | def connect_device(system: str, url: str):
class Operation (line 43) | class Operation(object):
method __init__ (line 44) | def __init__(self, test, device):
method find_element (line 49) | def find_element(self, ele):
class ElementNotFoundError (line 60) | class ElementNotFoundError(Exception):
class ElementNotDisappearError (line 64) | class ElementNotDisappearError(Exception):
FILE: core/app/device/assertionOpt.py
class Assertion (line 7) | class Assertion(Operation):
method assert_ele_exists (line 10) | def assert_ele_exists(self, element, assertion, expect):
method assert_ele_text (line 22) | def assert_ele_text(self, system, element, assertion, expect):
method assert_ele_attribute (line 37) | def assert_ele_attribute(self, element, attribute, assertion, expect):
method assert_ele_center (line 49) | def assert_ele_center(self, system, element, assertion, expect):
method assert_ele_x (line 66) | def assert_ele_x(self, system, element, assertion, expect):
method assert_ele_y (line 83) | def assert_ele_y(self, system, element, assertion, expect):
method assert_alert_exists (line 100) | def assert_alert_exists(self, assertion, expect):
method assert_alert_text (line 112) | def assert_alert_text(self, assertion, expect):
method custom (line 124) | def custom(self, **kwargs):
FILE: core/app/device/conditionOpt.py
class Condition (line 8) | class Condition(Operation):
method condition_ele_exists (line 11) | def condition_ele_exists(self, element, assertion, expect):
method condition_ele_text (line 23) | def condition_ele_text(self, system, element, assertion, expect):
method condition_ele_attribute (line 38) | def condition_ele_attribute(self, element, attribute, assertion, expect):
method condition_ele_center (line 50) | def condition_ele_center(self, system, element, assertion, expect):
method condition_ele_x (line 67) | def condition_ele_x(self, system, element, assertion, expect):
method condition_ele_y (line 84) | def condition_ele_y(self, system, element, assertion, expect):
method condition_alert_exists (line 101) | def condition_alert_exists(self, assertion, expect):
method condition_alert_text (line 113) | def condition_alert_text(self, assertion, expect):
method custom (line 125) | def custom(self, **kwargs):
FILE: core/app/device/relationOpt.py
class Relation (line 7) | class Relation(Operation):
method get_window_size (line 9) | def get_window_size(self, system, save_name):
method get_window_width (line 25) | def get_window_width(self, system, save_name):
method get_window_height (line 41) | def get_window_height(self, system, save_name):
method get_ele_text (line 57) | def get_ele_text(self, system, element, save_name):
method get_ele_center (line 71) | def get_ele_center(self, system, element, save_name):
method get_ele_x (line 87) | def get_ele_x(self, system, element, save_name):
method get_ele_y (line 103) | def get_ele_y(self, system, element, save_name):
method get_alert_text (line 119) | def get_alert_text(self, save_name):
method custom (line 130) | def custom(self, **kwargs):
FILE: core/app/device/scenarioOpt.py
class Scenario (line 7) | class Scenario(Operation):
method custom (line 10) | def custom(self, **kwargs):
FILE: core/app/device/systemOpt.py
class System (line 9) | class System(Operation):
method start_app (line 12) | def start_app(self, app_id):
method close_app (line 21) | def close_app(self, app_id):
method swipe_left (line 30) | def swipe_left(self, system):
method swipe_right (line 42) | def swipe_right(self, system):
method swipe_up (line 54) | def swipe_up(self, system):
method swipe_down (line 66) | def swipe_down(self, system):
method home (line 78) | def home(self, system):
method back (line 90) | def back(self):
method press (line 99) | def press(self, keycode):
method screenshot (line 108) | def screenshot(self, name):
method screen_on (line 118) | def screen_on(self, system):
method screen_off (line 130) | def screen_off(self, system):
method sleep (line 142) | def sleep(self, second):
method implicitly_wait (line 151) | def implicitly_wait(self, second):
method custom (line 160) | def custom(self, **kwargs):
FILE: core/app/device/viewOpt.py
class View (line 9) | class View(Operation):
method click (line 12) | def click(self, element):
method double_click (line 21) | def double_click(self, system, element):
method long_click (line 33) | def long_click(self, system, element, second):
method click_coord (line 48) | def click_coord(self, x, y):
method double_click_coord (line 57) | def double_click_coord(self, system, x, y):
method long_click_coord (line 69) | def long_click_coord(self, system, x, y, second):
method swipe (line 81) | def swipe(self, system, fx, fy, tx, ty, duration=None):
method input_text (line 97) | def input_text(self, element, text):
method clear_text (line 106) | def clear_text(self, system, element):
method scroll_to_ele (line 123) | def scroll_to_ele(self, system, element, direction):
method pinch_in (line 144) | def pinch_in(self, system, element):
method pinch_out (line 156) | def pinch_out(self, system, element):
method wait (line 168) | def wait(self, element, second):
method wait_gone (line 182) | def wait_gone(self, system, element, second):
method drag_to_ele (line 200) | def drag_to_ele(self, start_element, end_element):
method drag_to_coord (line 209) | def drag_to_coord(self, element, x, y):
method drag_coord (line 218) | def drag_coord(self, fx, fy, tx, ty):
method swipe_ele (line 227) | def swipe_ele(self, element, direction):
method alert_wait (line 236) | def alert_wait(self, second):
method alert_accept (line 245) | def alert_accept(self):
method alert_dismiss (line 254) | def alert_dismiss(self):
method alert_click (line 263) | def alert_click(self, name):
method custom (line 272) | def custom(self, **kwargs):
FILE: core/app/find_opt.py
function find_system_opt (line 9) | def find_system_opt(operate_name: str):
function find_view_opt (line 86) | def find_view_opt(operate_name: str):
function find_assertion_opt (line 194) | def find_assertion_opt(operate_name: str):
function find_relation_opt (line 253) | def find_relation_opt(operate_name: str):
function find_condition_opt (line 306) | def find_condition_opt(operate_name: str):
function find_scenario_opt (line 365) | def find_scenario_opt(operate_name: str):
FILE: core/app/testcase.py
class AppTestCase (line 9) | class AppTestCase:
method __init__ (line 10) | def __init__(self, test):
method execute (line 25) | def execute(self):
method loop_execute (line 34) | def loop_execute(self, opt_list, skip_opts, step_n=0):
method get_opt_content (line 67) | def get_opt_content(elements):
method before_execute (line 74) | def before_execute(self):
method after_execute (line 87) | def after_execute(self):
method render_looper (line 90) | def render_looper(self, looper):
method render_content (line 104) | def render_content(self, step):
FILE: core/app/teststep.py
class AppTestStep (line 7) | class AppTestStep:
method __init__ (line 8) | def __init__(self, test, device, context, collector):
method execute (line 15) | def execute(self):
method looper_controller (line 45) | def looper_controller(self, case, opt_list, step_n):
method assert_controller (line 72) | def assert_controller(self):
method condition_controller (line 90) | def condition_controller(self, current):
method log_show (line 108) | def log_show(self):
class NotExistedAppOperation (line 127) | class NotExistedAppOperation(Exception):
FILE: core/assertion.py
class LMAssert (line 8) | class LMAssert:
method __init__ (line 11) | def __init__(self, position, actual_result, expected_result):
method compare (line 16) | def compare(self):
method str2none (line 142) | def str2none(value):
method str2bool (line 149) | def str2bool(value):
method str2num (line 158) | def str2num(value):
method str2list (line 172) | def str2list(value):
method str2dict (line 193) | def str2dict(value):
method to_str (line 207) | def to_str(value):
method list_len (line 218) | def list_len(value):
class AssertionTypeNotExist (line 226) | class AssertionTypeNotExist(Exception):
FILE: core/template.py
class Template (line 13) | class Template:
method __init__ (line 15) | def __init__(self, test, context, functions, params, variable_start_st...
method init (line 36) | def init(self, data):
method set_help_data (line 41) | def set_help_data(self, url, path: str, headers: dict, query: dict, bo...
method render (line 48) | def render(self):
method _bytes_save (line 145) | def _bytes_save(self, value, flag):
method _bytes_slove (line 155) | def _bytes_slove(self, s, pattern):
method replace_param (line 161) | def replace_param(self, param):
method split_key (line 199) | def split_key(self, key: str):
method split_func (line 218) | def split_func(self, statement: str, flag: 'str' = '@'):
method concat (line 270) | def concat(start: int, arg_list: list, terminal_char: str):
class SplitFunctionError (line 290) | class SplitFunctionError(Exception):
FILE: core/web/collector.py
class WebOperationCollector (line 16) | class WebOperationCollector:
method __init__ (line 18) | def __init__(self):
method __parse (line 28) | def __parse(ui_data: dict, name):
method collect_id (line 33) | def collect_id(self, ui_data):
method collect_opt_type (line 36) | def collect_opt_type(self, ui_data):
method collect_opt_name (line 39) | def collect_opt_name(self, ui_data):
method collect_opt_trans (line 42) | def collect_opt_trans(self, ui_data):
method collect_opt_code (line 45) | def collect_opt_code(self, ui_data):
method collect_opt_element (line 48) | def collect_opt_element(self, ui_data):
method collect_opt_data (line 58) | def collect_opt_data(self, ui_data):
method collect (line 65) | def collect(self, ui_data):
FILE: core/web/driver/__init__.py
class Operation (line 4) | class Operation(object):
method __init__ (line 5) | def __init__(self, test, driver):
method find_element (line 10) | def find_element(self, ele):
method find_elements (line 20) | def find_elements(self, ele):
FILE: core/web/driver/assertionOpt.py
class Assertion (line 9) | class Assertion(Operation):
method assert_page_title (line 12) | def assert_page_title(self, assertion, expect):
method assert_page_url (line 24) | def assert_page_url(self, assertion, expect):
method assert_page_source (line 36) | def assert_page_source(self, assertion, expect):
method assert_ele_text (line 48) | def assert_ele_text(self, element, assertion, expect):
method assert_ele_tag (line 62) | def assert_ele_tag(self, element, assertion, expect):
method assert_ele_size (line 76) | def assert_ele_size(self, element, assertion, expect):
method assert_ele_height (line 90) | def assert_ele_height(self, element, assertion, expect):
method assert_ele_width (line 104) | def assert_ele_width(self, element, assertion, expect):
method assert_ele_location (line 118) | def assert_ele_location(self, element, assertion, expect):
method assert_ele_x (line 132) | def assert_ele_x(self, element, assertion, expect):
method assert_ele_y (line 146) | def assert_ele_y(self, element, assertion, expect):
method assert_ele_attribute (line 160) | def assert_ele_attribute(self, element, name, assertion, expect):
method assert_ele_selected (line 174) | def assert_ele_selected(self, element, assertion, expect):
method assert_ele_enabled (line 188) | def assert_ele_enabled(self, element, assertion, expect):
method assert_ele_displayed (line 202) | def assert_ele_displayed(self, element, assertion, expect):
method assert_ele_css (line 216) | def assert_ele_css(self, element, name, assertion, expect):
method assert_ele_existed (line 230) | def assert_ele_existed(self, element, assertion, expect):
method assert_window_position (line 246) | def assert_window_position(self, assertion, expect):
method assert_window_x (line 258) | def assert_window_x(self, assertion, expect):
method assert_window_y (line 270) | def assert_window_y(self, assertion, expect):
method assert_window_size (line 282) | def assert_window_size(self, assertion, expect):
method assert_window_width (line 294) | def assert_window_width(self, assertion, expect):
method assert_window_height (line 306) | def assert_window_height(self, assertion, expect):
method assert_cookies (line 318) | def assert_cookies(self, assertion, expect):
method assert_cookie (line 330) | def assert_cookie(self, name, assertion, expect):
method custom (line 342) | def custom(self, **kwargs):
FILE: core/web/driver/browserOpt.py
class Browser (line 12) | class Browser(Operation):
method max_window (line 15) | def max_window(self):
method min_window (line 24) | def min_window(self):
method full_window (line 33) | def full_window(self):
method set_position_window (line 42) | def set_position_window(self, x, y):
method set_size_window (line 52) | def set_size_window(self, width, height):
method switch_to_window (line 61) | def switch_to_window(self, window):
method close_window (line 70) | def close_window(self):
method save_screenshot (line 79) | def save_screenshot(self, name):
method click_to_new_window (line 89) | def click_to_new_window(self, element):
method back_and_close_window (line 112) | def back_and_close_window(self, window):
method open_url (line 122) | def open_url(self, domain, path):
method refresh (line 133) | def refresh(self):
method back (line 142) | def back(self):
method forward (line 151) | def forward(self):
method add_cookie (line 160) | def add_cookie(self, name, value):
method delete_cookie (line 169) | def delete_cookie(self, name):
method delete_cookies (line 178) | def delete_cookies(self):
method execute_script (line 187) | def execute_script(self, script, arg:tuple):
method execute_async_script (line 198) | def execute_async_script(self, script, arg:tuple):
method sleep (line 209) | def sleep(self, second):
method implicitly_wait (line 218) | def implicitly_wait(self, second):
method custom (line 227) | def custom(self, **kwargs):
FILE: core/web/driver/conditionOpt.py
class Condition (line 8) | class Condition(Operation):
method condition_page_title (line 11) | def condition_page_title(self, assertion, expect):
method condition_page_url (line 23) | def condition_page_url(self, assertion, expect):
method condition_page_source (line 35) | def condition_page_source(self, assertion, expect):
method condition_ele_text (line 47) | def condition_ele_text(self, element, assertion, expect):
method condition_ele_tag (line 61) | def condition_ele_tag(self, element, assertion, expect):
method condition_ele_size (line 75) | def condition_ele_size(self, element, assertion, expect):
method condition_ele_height (line 89) | def condition_ele_height(self, element, assertion, expect):
method condition_ele_width (line 103) | def condition_ele_width(self, element, assertion, expect):
method condition_ele_location (line 117) | def condition_ele_location(self, element, assertion, expect):
method condition_ele_x (line 131) | def condition_ele_x(self, element, assertion, expect):
method condition_ele_y (line 145) | def condition_ele_y(self, element, assertion, expect):
method condition_ele_attribute (line 159) | def condition_ele_attribute(self, element, name, assertion, expect):
method condition_ele_selected (line 173) | def condition_ele_selected(self, element, assertion, expect):
method condition_ele_enabled (line 187) | def condition_ele_enabled(self, element, assertion, expect):
method condition_ele_displayed (line 201) | def condition_ele_displayed(self, element, assertion, expect):
method condition_ele_css (line 215) | def condition_ele_css(self, element, name, assertion, expect):
method condition_ele_existed (line 229) | def condition_ele_existed(self, element, assertion, expect):
method condition_window_position (line 245) | def condition_window_position(self, assertion, expect):
method condition_window_x (line 257) | def condition_window_x(self, assertion, expect):
method condition_window_y (line 269) | def condition_window_y(self, assertion, expect):
method condition_window_size (line 281) | def condition_window_size(self, assertion, expect):
method condition_window_width (line 293) | def condition_window_width(self, assertion, expect):
method condition_window_height (line 305) | def condition_window_height(self, assertion, expect):
method condition_cookies (line 317) | def condition_cookies(self, assertion, expect):
method condition_cookie (line 329) | def condition_cookie(self, name, assertion, expect):
method custom (line 341) | def custom(self, **kwargs):
FILE: core/web/driver/pageOpt.py
class Page (line 12) | class Page(Operation):
method switch_frame (line 15) | def switch_frame(self, frame):
method switch_content (line 27) | def switch_content(self):
method switch_parent (line 36) | def switch_parent(self):
method alert_accept (line 45) | def alert_accept(self):
method alert_input (line 55) | def alert_input(self, text):
method alert_cancel (line 65) | def alert_cancel(self):
method free_click (line 75) | def free_click(self):
method clear (line 86) | def clear(self, element):
method input_text (line 97) | def input_text(self, element, text):
method click (line 108) | def click(self, element):
method submit (line 119) | def submit(self, element):
method click_and_hold (line 130) | def click_and_hold(self, element):
method context_click (line 142) | def context_click(self, element):
method double_click (line 154) | def double_click(self, element):
method drag_and_drop (line 166) | def drag_and_drop(self, start_element, end_element):
method drag_and_drop_by_offset (line 179) | def drag_and_drop_by_offset(self, element, x, y):
method key_down (line 191) | def key_down(self, element, value):
method key_up (line 207) | def key_up(self, element, value):
method move_by_offset (line 223) | def move_by_offset(self, x, y):
method move_to_element (line 234) | def move_to_element(self, element):
method move_to_element_with_offset (line 246) | def move_to_element_with_offset(self, element, x, y):
method release (line 258) | def release(self, element):
method wait_element_appear (line 270) | def wait_element_appear(self, element, second):
method wait_element_disappear (line 279) | def wait_element_disappear(self, element, second):
method custom (line 288) | def custom(self, **kwargs):
FILE: core/web/driver/relationOpt.py
class Relation (line 8) | class Relation(Operation):
method get_page_title (line 10) | def get_page_title(self, save_name):
method get_page_url (line 21) | def get_page_url(self, save_name):
method get_ele_text (line 32) | def get_ele_text(self, element, save_name):
method get_ele_tag (line 45) | def get_ele_tag(self, element, save_name):
method get_ele_size (line 58) | def get_ele_size(self, element, save_name):
method get_ele_height (line 71) | def get_ele_height(self, element, save_name):
method get_ele_width (line 84) | def get_ele_width(self, element, save_name):
method get_ele_location (line 97) | def get_ele_location(self, element, save_name):
method get_ele_x (line 110) | def get_ele_x(self, element, save_name):
method get_ele_y (line 123) | def get_ele_y(self, element, save_name):
method get_ele_attribute (line 136) | def get_ele_attribute(self, element, name, save_name):
method get_ele_css (line 149) | def get_ele_css(self, element, name, save_name):
method get_window_position (line 162) | def get_window_position(self, save_name):
method get_window_x (line 173) | def get_window_x(self, save_name):
method get_window_y (line 184) | def get_window_y(self, save_name):
method get_window_size (line 195) | def get_window_size(self, save_name):
method get_window_width (line 206) | def get_window_width(self, save_name):
method get_window_height (line 217) | def get_window_height(self, save_name):
method get_current_handle (line 228) | def get_current_handle(self, save_name):
method get_all_handle (line 239) | def get_all_handle(self, save_name):
method get_cookies (line 250) | def get_cookies(self, save_name):
method get_cookie (line 261) | def get_cookie(self, name, save_name):
method custom (line 272) | def custom(self, **kwargs):
FILE: core/web/driver/scenarioOpt.py
class Scenario (line 7) | class Scenario(Operation):
method custom (line 10) | def custom(self, **kwargs):
FILE: core/web/find_opt.py
function find_browser_opt (line 9) | def find_browser_opt(operate_name: str):
function find_page_opt (line 114) | def find_page_opt(operate_name: str):
function find_assertion_opt (line 230) | def find_assertion_opt(operate_name: str):
function find_relation_opt (line 366) | def find_relation_opt(operate_name: str):
function find_condition_opt (line 476) | def find_condition_opt(operate_name: str):
function find_scenario_opt (line 612) | def find_scenario_opt(operate_name: str):
FILE: core/web/testcase.py
class WebTestCase (line 9) | class WebTestCase:
method __init__ (line 10) | def __init__(self, test):
method execute (line 25) | def execute(self):
method loop_execute (line 34) | def loop_execute(self, opt_list, skip_opts, step_n=0):
method get_opt_content (line 67) | def get_opt_content(elements):
method before_execute (line 74) | def before_execute(self):
method after_execute (line 121) | def after_execute(self):
method render_driver (line 128) | def render_driver(self, driver_setting):
method render_looper (line 132) | def render_looper(self, looper):
method render_content (line 146) | def render_content(self, step):
FILE: core/web/teststep.py
class WebTestStep (line 7) | class WebTestStep:
method __init__ (line 8) | def __init__(self, test, driver, context, collector):
method execute (line 15) | def execute(self):
method looper_controller (line 44) | def looper_controller(self, case, opt_list, step_n):
method assert_controller (line 71) | def assert_controller(self):
method condition_controller (line 89) | def condition_controller(self, current):
method log_show (line 107) | def log_show(self):
class NotExistedWebOperation (line 126) | class NotExistedWebOperation(Exception):
FILE: lm/lm_api.py
class Api (line 9) | class Api(object):
method __init__ (line 11) | def __init__(self):
method request (line 18) | def request(self, url, data):
method download (line 23) | def download(self, url):
method save_token (line 29) | def save_token(token):
method load_header (line 35) | def load_header():
class LMApi (line 41) | class LMApi(Api):
method apply_token (line 43) | def apply_token(self):
method fetch_task (line 67) | def fetch_task(self):
method upload_result (line 95) | def upload_result(self, task_id, data_type, result):
method complete_task (line 125) | def complete_task(self, task_id):
method download_task_file (line 154) | def download_task_file(self, path):
method download_test_file (line 179) | def download_test_file(self, uuid):
method upload_screen_shot (line 204) | def upload_screen_shot(self,task_image_path, uuid, log_path):
FILE: lm/lm_case.py
class LMCase (line 16) | class LMCase(unittest.TestCase):
method __init__ (line 18) | def __init__(self, case_name, test_data, case_type="API"):
method testEntrance (line 25) | def testEntrance(self):
method doCleanups (line 33) | def doCleanups(self):
method debugLog (line 37) | def debugLog(self, log_info):
method errorLog (line 49) | def errorLog(self, log_info):
method recordTransDuring (line 61) | def recordTransDuring(self, during):
method defineTrans (line 66) | def defineTrans(self, id, name, content="", desc=None):
method complete_output (line 84) | def complete_output(self):
method deleteTrans (line 93) | def deleteTrans(self, index):
method updateTransStatus (line 98) | def updateTransStatus(self, status):
method recordFailStatus (line 102) | def recordFailStatus(self, exc_info=None):
method recordErrorStatus (line 109) | def recordErrorStatus(self, exc_info=None):
method saveScreenShot (line 124) | def saveScreenShot(self, name, screen_shot):
method handleResult (line 142) | def handleResult(self):
FILE: lm/lm_config.py
class IniReader (line 14) | class IniReader:
method __init__ (line 16) | def __init__(self, config_ini=CONFIG_PATH):
method data (line 22) | def data(self, section, option):
method option (line 28) | def option(self, section):
method modify (line 37) | def modify(self, section, option, value):
class LMConfig (line 44) | class LMConfig(object):
method __init__ (line 46) | def __init__(self, path=CONFIG_PATH):
FILE: lm/lm_log.py
class LMLogger (line 8) | class LMLogger(object):
method __init__ (line 10) | def __init__(self, logger_name='Auto Test'):
method get_handler (line 15) | def get_handler(self, file_path):
function DebugLogger (line 29) | def DebugLogger(log_info, file_path=default_log_path):
function ErrorLogger (line 42) | def ErrorLogger(log_info, file_path=default_log_path):
FILE: lm/lm_report.py
class LMReport (line 10) | class LMReport(object):
method __init__ (line 11) | def __init__(self, message_queue, case_result_queue):
method monitor_result (line 16) | def monitor_result(self):
method post_stop (line 57) | def post_stop(self, task_id=None):
FILE: lm/lm_result.py
class LMResult (line 8) | class LMResult(unittest.TestResult):
method __init__ (line 10) | def __init__(self, result, lock, queue):
method startTest (line 19) | def startTest(self, test):
method setupStdout (line 25) | def setupStdout(self):
method stopTest (line 29) | def stopTest(self, test):
method restoreStdout (line 51) | def restoreStdout(self):
method addSuccess (line 55) | def addSuccess(self, test):
method addFailure (line 59) | def addFailure(self, test, err):
method addError (line 64) | def addError(self, test, err):
method addSkip (line 69) | def addSkip(self, test, reason):
method mergeResult (line 73) | def mergeResult(self, n, test, e):
FILE: lm/lm_run.py
class LMRun (line 8) | class LMRun(object):
method __init__ (line 9) | def __init__(self, plan_tuple, run_index, default_result, default_lock...
method run_test (line 16) | def run_test(self):
FILE: lm/lm_setting.py
class LMSetting (line 13) | class LMSetting(object):
method __init__ (line 14) | def __init__(self, task):
method data_pull (line 19) | def data_pull(self):
method file_unzip (line 38) | def file_unzip(self, file_path):
method task_analysis (line 46) | def task_analysis(self):
method create_thread (line 91) | def create_thread(self, plan, queue, current_exec_status):
method read_fail_case (line 117) | def read_fail_case(test_plan, result):
class LMSession (line 134) | class LMSession(object):
method __init__ (line 136) | def __init__(self):
class LMDriver (line 140) | class LMDriver(object):
method __init__ (line 142) | def __init__(self):
FILE: lm/lm_start.py
class LMStart (line 15) | class LMStart(object):
method __init__ (line 17) | def __init__(self):
method main (line 22) | def main(self):
method send_heartbeat (line 49) | def send_heartbeat(self, queue):
method fetch_task (line 72) | def fetch_task(self, queue):
method monitor_message (line 85) | def monitor_message(self, message_queue, task_queue):
method run_test (line 115) | def run_test(task, queue, current_exec_status):
method push_result (line 121) | def push_result(message_queue, case_result_queue):
method upload_image (line 126) | def upload_image(task, current_exec_status):
FILE: lm/lm_upload.py
class LMUpload (line 7) | class LMUpload(object):
method __init__ (line 9) | def __init__(self, files, log_path):
method set_upload (line 14) | def set_upload(self, task_image_path):
method upload (line 29) | def upload(self, task_image_path, uuid, file):
FILE: lm/lm_ws.py
class Client (line 8) | class Client(WebSocketClient):
method __init__ (line 10) | def __init__(self, url, queue):
method opened (line 15) | def opened(self):
method closed (line 20) | def closed(self, code, reason=None):
method received_message (line 25) | def received_message(self, resp):
FILE: tools/funclib/__init__.py
function get_func_lib (line 5) | def get_func_lib(test=None, lm_func=None, context=None, params=None):
FILE: tools/funclib/load_faker.py
class CustomFaker (line 9) | class CustomFaker(Faker):
method __init__ (line 10) | def __init__(self, package='provider', test=None, lm_func=None, temp=N...
method __call__ (line 23) | def __call__(self, name, *args, **kwargs):
method _read_module (line 26) | def _read_module(self):
method _load_module (line 35) | def _load_module(self):
method _load_lm_func (line 46) | def _load_lm_func(self):
method _lm_custom_func (line 70) | def _lm_custom_func(self, code, params, test, temp):
FILE: tools/funclib/provider/lm_provider.py
class LiuMaProvider (line 14) | class LiuMaProvider(BaseProvider):
method loadfile (line 17) | def loadfile(uuid):
method savefile (line 26) | def savefile(uuid):
method b64encode_str (line 45) | def b64encode_str(s: str):
method b64encode_bytes (line 49) | def b64encode_bytes(s: bytes):
method b64encode_file (line 52) | def b64encode_file(self, uuid):
method b64decode_toStr (line 57) | def b64decode_toStr(s: str):
method b64decode_toBytes (line 61) | def b64decode_toBytes(s: str):
method arithmetic (line 65) | def arithmetic(expression: str):
method current_time (line 72) | def current_time(s: str = '%Y-%m-%d'):
method year_shift (line 78) | def year_shift(shift, s: str = '%Y-%m-%d'):
method month_shift (line 86) | def month_shift(shift, s: str = '%Y-%m-%d'):
method week_shift (line 94) | def week_shift(shift, s: str = '%Y-%m-%d'):
method date_shift (line 103) | def date_shift(shift, s: str = '%Y-%m-%d'):
method hour_shift (line 112) | def hour_shift(shift, s: str = '%Y-%m-%d %H:%M:%S'):
method minute_shift (line 121) | def minute_shift(shift, s: str = '%Y-%m-%d %H:%M:%S'):
method second_shift (line 130) | def second_shift(shift, s: str = '%Y-%m-%d %H:%M:%S'):
method lenof (line 139) | def lenof(array):
method indexof (line 143) | def indexof(array, index):
method keyof (line 147) | def keyof(map, key):
method pinyin (line 151) | def pinyin(cname: str):
method substing (line 155) | def substing(s, start: int=0, end: int=-1):
method extract (line 159) | def extract(data):
method replace (line 163) | def replace(s, old, new):
method map_dumps (line 167) | def map_dumps(tar):
method array_dumps (line 171) | def array_dumps(tar):
FILE: tools/utils/sql.py
class SQLConnect (line 8) | class SQLConnect:
method __init__ (line 10) | def __init__(self, tpz, host, port, db, user, password):
method connect (line 19) | def connect(self):
method query (line 44) | def query(self, sql):
method exec (line 64) | def exec(self, sql):
FILE: tools/utils/utils.py
function extract_by_jsonpath (line 8) | def extract_by_jsonpath(data: (dict,list, str), expression: str):
function extract_by_regex (line 18) | def extract_by_regex(data: (dict, str), pattern: str):
function quotation_marks (line 30) | def quotation_marks(s):
function url_join (line 49) | def url_join(host: str, path: str):
function proxies_join (line 55) | def proxies_join(proxies: dict):
function extract (line 79) | def extract(name: str, data: (dict, list, str), expression: str):
function get_case_message (line 88) | def get_case_message(data):
function handle_operation_data (line 99) | def handle_operation_data(data_type, data_value):
function handle_params_data (line 123) | def handle_params_data(params):
function handle_form_data (line 148) | def handle_form_data(form):
function handle_files (line 175) | def handle_files(files):
function json_to_path (line 184) | def json_to_path(data):
function relate_sort (line 211) | def relate_sort(data, data_from):
function get_json_relation (line 248) | def get_json_relation(data: dict, data_from: str):
class ExtractValueError (line 252) | class ExtractValueError(Exception):
class ProxiesError (line 256) | class ProxiesError(Exception):
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (359K chars).
[
{
"path": ".gitignore",
"chars": 64,
"preview": "**/__pycache__/\n\n.idea\n\nimage\n\nlog\n\ndata\n\nfile\n\nbrowser/**.exe\n\n"
},
{
"path": "Dockerfile",
"chars": 338,
"preview": "FROM python:3.8\n\nMAINTAINER \"liuma\"\n\nCOPY browser /liuma/browser\n\nCOPY core /liuma/core\n\nCOPY requirements.txt /liuma/\n\n"
},
{
"path": "LICENSE",
"chars": 34457,
"preview": " GNU AFFERO GENERAL PUBLIC LICENSE\n Version 3, 19 November 2007\n\n Copyright (C)"
},
{
"path": "README.md",
"chars": 2069,
"preview": "# 流马-低代码测试平台\n## 一、项目概述\n\n流马是一款低代码自动化测试平台,旨在采用最简单的架构统一支持API/WebUI/AppUI的自动化测试。平台采用低代码设计模式,将传统测试脚本以配置化实现,从而让代码能力稍弱的用户快速上手自动"
},
{
"path": "browser/readme.md",
"chars": 246,
"preview": "### Chromedriver\n \n selenium驱动谷歌浏览器依赖于chromedriver, 因此启动引擎前需要下载驱动。\n\n+ 下载地址\n\n 推荐使用淘宝下载源[下载链接](http://npm.taobao.org/"
},
{
"path": "config/config.ini",
"chars": 266,
"preview": "[Platform]\nurl = http://127.0.0.1:8080\nenable-stderr = true\n\n[Engine]\nengine-code = ******\nengine-secret = ******\n\n[Head"
},
{
"path": "core/api/collector.py",
"chars": 8365,
"preview": "import json\nimport re\n\nfrom tools.utils.utils import proxies_join, handle_form_data, handle_files\n\n\nclass ApiRequestColl"
},
{
"path": "core/api/testcase.py",
"chars": 8900,
"preview": "import re\nimport sys\n\nfrom core.api.collector import ApiRequestCollector\nfrom core.template import Template\nfrom core.ap"
},
{
"path": "core/api/teststep.py",
"chars": 12807,
"preview": "import datetime\nimport sys\nfrom time import sleep\n\nfrom requests import request, Session\nfrom copy import deepcopy\nimpor"
},
{
"path": "core/app/collector.py",
"chars": 2708,
"preview": "import json\n\n\nclass AppOperationCollector:\n\n def __init__(self):\n self.id = None\n self.opt_type = None\n"
},
{
"path": "core/app/device/__init__.py",
"chars": 1817,
"preview": "from typing import Optional\nfrom uiautomator2 import Device\nfrom wda import Client, AlertAction, BaseClient\n\n\nclass Andr"
},
{
"path": "core/app/device/assertionOpt.py",
"chars": 5949,
"preview": "import sys\nfrom uiautomator2 import UiObjectNotFoundError\nfrom core.assertion import LMAssert\nfrom core.app.device impor"
},
{
"path": "core/app/device/conditionOpt.py",
"chars": 5977,
"preview": "import sys\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom core.assertion import LMAssert\nfrom core.app.device impo"
},
{
"path": "core/app/device/relationOpt.py",
"chars": 5766,
"preview": "import sys\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom core.app.device import Operation\n\n\nclass Relation(Operat"
},
{
"path": "core/app/device/scenarioOpt.py",
"chars": 1501,
"preview": "import sys\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom core.app.device import Operation\n\n\nclass Scenario(Operat"
},
{
"path": "core/app/device/systemOpt.py",
"chars": 5840,
"preview": "import sys\nfrom time import sleep\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom wda import WDAElementNotFoundErro"
},
{
"path": "core/app/device/viewOpt.py",
"chars": 10282,
"preview": "import sys\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom uiautomator2.xpath import XPath\nfrom wda import WDAEleme"
},
{
"path": "core/app/find_opt.py",
"chars": 13455,
"preview": "from core.app.device.viewOpt import View\nfrom core.app.device.systemOpt import System\nfrom core.app.device.scenarioOpt i"
},
{
"path": "core/app/testcase.py",
"chars": 5231,
"preview": "from core.template import Template\nfrom core.app.collector import AppOperationCollector\nfrom core.app.teststep import Ap"
},
{
"path": "core/app/teststep.py",
"chars": 5884,
"preview": "import sys\nfrom datetime import datetime\nfrom core.app.find_opt import *\nfrom core.assertion import LMAssert\n\n\nclass App"
},
{
"path": "core/assertion.py",
"chars": 14103,
"preview": "# -*- coding: utf-8 -*-\nimport re\nimport ast\n\nfrom assertpy import assertpy\n\n\nclass LMAssert:\n \"\"\"断言\"\"\"\n\n def __in"
},
{
"path": "core/template.py",
"chars": 12306,
"preview": "from functools import reduce\nfrom hashlib import md5\nfrom jsonpath import jsonpath\nfrom jsonpath_ng.parser import JsonPa"
},
{
"path": "core/web/collector.py",
"chars": 2248,
"preview": "from selenium.webdriver.common.by import By\n\n\nlocator = {\n \"ID\": By.ID,\n \"XPATH\": \"xpath\",\n \"LINK\": \"link text\""
},
{
"path": "core/web/driver/__init__.py",
"chars": 1146,
"preview": "from selenium.common.exceptions import NoSuchElementException\n\n\nclass Operation(object):\n def __init__(self, test, dr"
},
{
"path": "core/web/driver/assertionOpt.py",
"chars": 13655,
"preview": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\n\nfrom core.assertion import LMAssert\nfrom core"
},
{
"path": "core/web/driver/browserOpt.py",
"chars": 8458,
"preview": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\n\nfrom core.web.driver import Operation\nfrom da"
},
{
"path": "core/web/driver/conditionOpt.py",
"chars": 13703,
"preview": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\nfrom core.assertion import LMAssert\nfrom core."
},
{
"path": "core/web/driver/pageOpt.py",
"chars": 11247,
"preview": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\nfrom selenium.webdriver import ActionChains\nfr"
},
{
"path": "core/web/driver/relationOpt.py",
"chars": 10475,
"preview": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\n\nfrom core.web.driver import Operation\n\n\nclass"
},
{
"path": "core/web/driver/scenarioOpt.py",
"chars": 1518,
"preview": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\nfrom core.web.driver import Operation\n\n\nclass "
},
{
"path": "core/web/find_opt.py",
"chars": 25113,
"preview": "from core.web.driver.browserOpt import Browser\nfrom core.web.driver.pageOpt import Page\nfrom core.web.driver.scenarioOpt"
},
{
"path": "core/web/testcase.py",
"chars": 7349,
"preview": "import re\nfrom selenium import webdriver\nfrom core.template import Template\nfrom core.web.collector import WebOperationC"
},
{
"path": "core/web/teststep.py",
"chars": 5835,
"preview": "import sys\nfrom datetime import datetime\nfrom core.assertion import LMAssert\nfrom core.web.find_opt import *\n\n\nclass Web"
},
{
"path": "lm/lm_api.py",
"chars": 8782,
"preview": "# -*- coding: utf-8 -*-\nimport base64\nimport requests\nimport time\nfrom lm.lm_config import *\nfrom lm.lm_log import Debug"
},
{
"path": "lm/lm_case.py",
"chars": 7238,
"preview": "# -*- coding: utf-8 -*-\nimport io\nimport os\nimport datetime\nimport sys\nimport time\nimport unittest\nimport traceback\nfrom"
},
{
"path": "lm/lm_config.py",
"chars": 2151,
"preview": "# -*- coding: utf-8 -*-\nimport os\nimport configparser\n\nBASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__fil"
},
{
"path": "lm/lm_log.py",
"chars": 1639,
"preview": "# -*- coding: utf-8 -*-\nimport os\nimport logging\nimport threading\nfrom lm.lm_config import LOG_PATH\n\n\nclass LMLogger(obj"
},
{
"path": "lm/lm_report.py",
"chars": 2758,
"preview": "# -*- coding: utf-8 -*-\nimport datetime, time\nimport os\nimport shutil\nfrom lm.lm_api import LMApi\nfrom lm.lm_log import "
},
{
"path": "lm/lm_result.py",
"chars": 2618,
"preview": "# -*- coding: utf-8 -*-\nimport datetime\nimport io\nimport sys\nimport unittest\n\n\nclass LMResult(unittest.TestResult):\n\n "
},
{
"path": "lm/lm_run.py",
"chars": 1621,
"preview": "# -*- coding: utf-8 -*-\nimport unittest, threading\nfrom lm import lm_case, lm_result\nfrom lm.lm_log import ErrorLogger\nf"
},
{
"path": "lm/lm_setting.py",
"chars": 5841,
"preview": "# -*- coding: utf-8 -*-\nimport os\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom "
},
{
"path": "lm/lm_start.py",
"chars": 6514,
"preview": "# -*- coding: utf-8 -*-\nimport threading\nfrom multiprocessing import Process, Queue, Value\nimport time, os\nfrom lm.lm_ap"
},
{
"path": "lm/lm_upload.py",
"chars": 981,
"preview": "# -*- coding: utf-8 -*-\nimport os\nimport threading\nfrom lm.lm_api import LMApi\n\n\nclass LMUpload(object):\n\n def __init"
},
{
"path": "lm/lm_ws.py",
"chars": 1031,
"preview": "import json\nimport os\nfrom ws4py.client.threadedclient import WebSocketClient\nfrom lm.lm_config import LOG_PATH\nfrom lm."
},
{
"path": "requirements.txt",
"chars": 406,
"preview": "assertpy==1.1\ncertifi==2020.6.20\nchardet==3.0.4\ndecorator==5.0.5\nFaker==6.0.0\nidna==2.10\njsonpath==0.82\njsonpath-ng==1.5"
},
{
"path": "startup.py",
"chars": 294,
"preview": "from lm.lm_start import LMStart\n\n\n__version__ = \"1.4.1\"\n\n\nif __name__ == '__main__':\n print(\"------------------------"
},
{
"path": "tools/funclib/__init__.py",
"chars": 348,
"preview": "from .load_faker import CustomFaker\nimport time\n\n\ndef get_func_lib(test=None, lm_func=None, context=None, params=None):\n"
},
{
"path": "tools/funclib/load_faker.py",
"chars": 3827,
"preview": "import os\nfrom faker import Faker\nfrom importlib import import_module, reload\nimport sys\nfrom faker.providers import Bas"
},
{
"path": "tools/funclib/params_enum.py",
"chars": 1877,
"preview": "PARAMS_ENUM = {\n \"bothify\": [str, str],\n \"lexify\": [str, str],\n \"numerify\": [str],\n \"random_int\": [int, int,"
},
{
"path": "tools/funclib/provider/lm_provider.py",
"chars": 5038,
"preview": "import os\nfrom functools import reduce\nfrom faker.providers import BaseProvider\nimport time\nfrom lm.lm_api import LMApi\n"
},
{
"path": "tools/utils/sql.py",
"chars": 2384,
"preview": "import decimal\nimport pymssql as mssql\nimport pymysql as mysql\nimport psycopg2 as pgsql\nimport cx_Oracle as oracle\n\n\ncla"
},
{
"path": "tools/utils/utils.py",
"chars": 8650,
"preview": "import json\nimport re\nfrom urllib.parse import quote\nimport jsonpath\nimport copy\n\n\ndef extract_by_jsonpath(data: (dict,l"
}
]
About this extraction
This page contains the full source code of the Chras-fu/Liuma-engine GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (315.5 KB), approximately 74.0k tokens, and a symbol index with 509 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.