[
  {
    "path": ".gitignore",
    "content": "**/__pycache__/\n\n.idea\n\nimage\n\nlog\n\ndata\n\nfile\n\nbrowser/**.exe\n\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.8\n\nMAINTAINER \"liuma\"\n\nCOPY browser /liuma/browser\n\nCOPY core /liuma/core\n\nCOPY requirements.txt /liuma/\n\nCOPY tools/ /liuma/tools\n\nCOPY lm/ /liuma/lm\n\nCOPY startup.py /liuma/\n\nWORKDIR /liuma\n\nRUN pip install -r requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com\n\nCMD [\"python\", \"startup.py\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    Copyright (c) 2022 The Liuma Project of Chras-fu\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "# 流马-低代码测试平台\n## 一、项目概述\n\n流马是一款低代码自动化测试平台，旨在采用最简单的架构统一支持API/WebUI/AppUI的自动化测试。平台采用低代码设计模式，将传统测试脚本以配置化实现，从而让代码能力稍弱的用户快速上手自动化测试。同时平台也支持通过简单的代码编写实现自定义组件，使用户可以灵活实现自己的需求。\n\n本项目分为平台端和引擎端，采用分布式执行设计，可以将测试执行的节点(即引擎)注册在任意环境的任意一台机器上，从而突破资源及网络限制。同时，通过将引擎启动在本地PC上，方便用户快速调试测试用例，实时查看执行过程，带来传统脚本编写一致的便捷。\n\n在线体验: [演示平台](http://demo-ee.liumatest.cn)\n\n官网地址: [流马官网](http://www.liumatest.cn)\n\n社区地址: [流马社区](http://community.liumatest.cn)\n\n配套开发教程: [B站课堂](https://www.bilibili.com/cheese/play/ss7009)\n\n如果本项目对您有帮助，请给我们一个Star，您的支持是我们前进的动力。\n\n如果您需要二次开发，请务必遵循AGPL开源协议，并保留版权信息。我们保留一切对于侵权行为追责的权利。\n\n## 二、功能介绍\n\n![system](https://user-images.githubusercontent.com/96771570/221833391-9d35308a-3f90-47c7-9e9d-e62fc1201f18.png)\n\n1. API测试\n```\n(1) 支持单接口测试和链路测试。\n(2) 支持接口统一管理，支持postman/swagger导入。\n(3) 支持一键生成字段校验的接口健壮性用例。\n(4) 支持全局变量、关联、断言、内置函数、自定义函数。\n(5) 支持前后置脚本、失败继续、超时时间、等待/条件/循环等逻辑控制器。\n(6) 支持环境与用例解耦，多种方式匹配域名，让一套用例可以在多个环境上执行。\n```\n\n2. WebUI测试\n```\n(1) 支持关键字驱动，零代码编写用例。\n(2) 支持UI元素统一管理，Excel模板批量导入。\n(3) 支持自定义关键字，封装公共的操作步骤，提升用例可读性。\n(4) 支持本地引擎执行，实时查看执行过程。\n(5) 支持与API用例在同一用例集合顺序执行。\n```\n\n3. AppUI测试\n```\n(1) 支持WebUI同等用例编写和执行能力\n(2) 支持安卓和苹果系统\n(3) 支持持真机管理、投屏和在线操作\n(4) 支持控件元素在线获取，一键保存元素\n(5) 支持实时查看执行过程\n```\n\n更多功能及详细请参考: [用户手册](http://www.liumatest.cn/productDoc)\n\n\n## 三、开发环境\n\n环境依赖: Python3.8、Chrome、ChromeDriver(参考:[驱动说明](./browser/readme.md))\n\nIDE推荐: python使用pyCharm\n\n1. 引擎启动\n```\nStep1: 安装依赖包 pip3 install -r requirements.txt\n\nStep2: 流马测试平台->引擎管理->注册引擎 保存engine-code和engine-secret\n\nStep3: engine-code和engine-secret填写在/config/config.ini文件中对应位置\n\nStep4: 修改/config/config.ini文件中Platform->url为后端地址\n\nStep5: 如linux启动，修改/config/config.ini文件中WebDriver->options为headless\n\nStep6: 如linux/mac启动，修改/config/config.ini文件中WebDriver->path为chromedriver\n\nStep7: 启动引擎 python3 startup.py\n```\n\n2. 验证启动\n\n平台引擎管理查看自己的引擎，显示在线，证明启动成功。再编写一个简单的接口用例并执行，执行成功并返回报告，引擎注册完成。\n\n## 四、容器部署\n\n容器部署请参考: [部署手册](http://www.liumatest.cn/deployDoc)\n\n\n## 五、关于我们\n\n流马秉持着帮助中小企业的测试团队快速建立自动化体系的目标，将会不断迭代并吸取用户的建议，欢迎大家给我们提出宝贵的意见。\n\n如需学习平台开发相关内容或在线交流，可关注个人微信公众号【流马测试】\n\n![qr](https://user-images.githubusercontent.com/96771570/161195670-3868f409-ed49-431f-8650-185e3e179679.png)\n\n\n"
  },
  {
    "path": "browser/readme.md",
    "content": "### Chromedriver\n    \n  selenium驱动谷歌浏览器依赖于chromedriver, 因此启动引擎前需要下载驱动。\n\n+ 下载地址\n\n  推荐使用淘宝下载源[下载链接](http://npm.taobao.org/mirrors/chromedriver/)\n\n\n+ 使用说明\n    \n  chromedriver版本需要与引擎所在机器安装的chrome浏览器相对应，且浏览器尽量不要使用最新版本。\n\n  将chromedriver下载后，复制到当前目录下即可。\n"
  },
  {
    "path": "config/config.ini",
    "content": "[Platform]\nurl = http://127.0.0.1:8080\nenable-stderr = true\n\n[Engine]\nengine-code = ******\nengine-secret = ******\n\n[Header]\ncontent-type = application/json;charset=utf-8\ntoken = ******\n\n[WebDriver]\noptions = normal\npath = chromedriver.exe\n\n[RunSetting]\nmax-run = 2\n\n"
  },
  {
    "path": "core/api/collector.py",
    "content": "import json\nimport re\n\nfrom tools.utils.utils import proxies_join, handle_form_data, handle_files\n\n\nclass ApiRequestCollector:\n\n    def __init__(self):\n        self.apiId = None\n        self.apiName = None\n        self.method = None\n        self.url = None\n        self.path = None\n        self.protocol = None\n        self.body_type = None\n        self.others = {}\n        self.controller = {}\n        self.looper = {}\n        self.conditions = []\n        self.assertions = []\n        self.relations = []\n\n    def collect_flag(self, api_data, arg_name):\n        if arg_name not in api_data or api_data[arg_name] is None:\n            raise NotExistedFieldError('接口数据{}字段不存在或为空'.format(arg_name))\n        elif type(api_data[arg_name]) is str and len(api_data[arg_name]) == 0:\n            raise NotExistedFieldError('接口数据{}字段长度为0'.format(arg_name))\n        else:\n            setattr(self, arg_name, api_data[arg_name])\n\n    def collect_other(self, api_data, arg_name, func=lambda x: x):\n        if arg_name not in api_data or api_data[arg_name] is None or len(api_data[arg_name]) == 0:\n            self.others[arg_name] = None\n        else:\n            self.others[arg_name] = func(api_data[arg_name])\n\n    def collect_context(self, api_data, arg_name):\n        if arg_name not in api_data or api_data[arg_name] is None or len(api_data[arg_name]) == 0:\n            setattr(self, arg_name, None)\n        else:\n            setattr(self, arg_name, api_data[arg_name])\n\n    def collect_id(self, api_data):\n        self.collect_flag(api_data, \"apiId\")\n\n    def collect_name(self, api_data):\n        self.collect_flag(api_data, \"apiName\")\n\n    def collect_protocol(self, api_data):\n        self.collect_flag(api_data, \"protocol\")\n\n    def collect_method(self, api_data):\n        if 'method' not in api_data or api_data['method'] is None or len(api_data['method']) == 0:\n            raise UnDefinableMethodError(\"接口{}未定义请求方法\".format(api_data['apiId']))\n        method = api_data['method'].upper()\n\n        self.method = method\n\n    def collect_url(self, api_data):\n        if 'url' not in api_data:\n            raise UnDefinablePathError(\"接口{}未设置域名\".format(api_data['apiId']))\n        else:\n            self.url = api_data['url']\n\n    def collect_path(self, api_data):\n        if 'path' not in api_data:\n            raise UnDefinablePathError(\"接口{}未设置路径\".format(api_data['apiId']))\n        else:\n            fields = re.findall(r'\\{(.*?)\\}', api_data['path'])\n            path = api_data['path']\n            for field in fields:\n                result = \"{%s}\" % field\n                if field in api_data['rest']:\n                    result = api_data[\"rest\"][field]  # 将path中的参数替换成rest\n                if \"#{%s}\" % field in path: # 兼容老版本#{name}\n                    path = path.replace(\"#{%s}\" % field, result)\n                else:\n                    path = path.replace(\"{%s}\" % field, result)\n            self.path = path\n\n    def collect_controller(self, api_data):\n        if \"sleepBeforeRun\" not in api_data[\"controller\"]:\n            api_data[\"controller\"][\"sleepBeforeRun\"] = 0  # 默认执行前不等待\n        if \"sleepAfterRun\" not in api_data[\"controller\"]:\n            api_data[\"controller\"][\"sleepAfterRun\"] = 0  # 默认执行完成不等待\n        if \"useSession\" not in api_data[\"controller\"]:\n            api_data[\"controller\"][\"useSession\"] = \"false\"  # 默认不使用session\n        if \"saveSession\" not in api_data[\"controller\"]:\n            api_data[\"controller\"][\"saveSession\"] = \"false\"  # 默认不保存session\n        if \"pre\" not in api_data[\"controller\"]:\n            api_data[\"controller\"][\"pre\"] = None  # 默认没有前置脚本和sql\n        if \"post\" not in api_data[\"controller\"]:\n            api_data[\"controller\"][\"post\"] = None  # 默认没有后置脚本和sql\n        if \"errorContinue\" not in api_data[\"controller\"]:\n            api_data[\"controller\"][\"errorContinue\"] = \"false\"  # 默认错误后不再执行\n        self.controller = api_data[\"controller\"]\n\n    def collect_conditions(self, api_data):\n        if \"whetherExec\" in api_data[\"controller\"]:\n            self.conditions = json.loads(api_data[\"controller\"][\"whetherExec\"])\n\n    def collect_looper(self, api_data):\n        if \"loopExec\" in api_data[\"controller\"]:\n            self.looper = json.loads(api_data[\"controller\"][\"loopExec\"])\n\n    def collect_query(self, api_data):\n        if len(api_data[\"query\"]) > 0:\n            self.others[\"params\"] = api_data[\"query\"]\n        else:\n            self.others[\"params\"] = None\n\n    def collect_headers(self, api_data):\n        self.collect_other(api_data, 'headers')\n\n    def collect_cookies(self, api_data):\n        if self.others['headers'] is not None:\n            pop_key = None\n            for key in self.others['headers']:\n                if key.strip().lower() in ['cookie', 'cookies']:\n                    pop_key = key\n                    break\n            if pop_key is not None:\n                value = self.others['headers'].pop(pop_key)\n                self.others['headers']['cookie'] = value\n\n    def collect_proxies(self, api_data):\n        self.collect_other(api_data, 'proxies', proxies_join)\n\n    def collect_body(self, api_data):\n        body = api_data[\"body\"]\n        if body is None:\n            return\n        self.body_type = body[\"type\"]\n        if body[\"type\"] == \"json\":\n            if body[\"json\"] != '':\n                body_json = json.loads(body[\"json\"])\n                if len(body_json) > 0:\n                    self.others[\"json\"] = body_json\n        elif body[\"type\"] in (\"form-urlencoded\", \"form-data\"):\n            body_data, body_file = handle_form_data(body[\"form\"])\n            if len(body_data) > 0:\n                self.others[\"data\"] = body_data\n            if len(body_file) > 0:\n                self.others[\"files\"] = body_file\n        elif body[\"type\"] in (\"text\", \"xml\", \"html\"):\n            if body[\"raw\"] != \"\":\n                self.others[\"data\"] = body[\"raw\"]\n        elif body[\"type\"] == \"file\":\n            files = handle_files(body[\"file\"])\n            if len(files) > 0:\n                self.others[\"files\"] = files\n\n    def collect_stream(self, api_data):\n        if \"requireStream\" in api_data[\"controller\"]:\n            if api_data[\"controller\"][\"requireStream\"].lower() == \"true\":\n                self.others[\"stream\"] = True\n            else:\n                self.others[\"stream\"] = False\n        else:\n            self.others[\"stream\"] = None\n\n    def collect_verify(self, api_data):\n        if \"requireVerify\" in api_data[\"controller\"]:\n            if api_data[\"controller\"][\"requireVerify\"].lower() == \"true\":\n                self.others[\"verify\"] = True\n            else:\n                self.others[\"verify\"] = False\n        else:\n            self.others[\"verify\"] = None\n\n    def collect_auth(self, api_data):\n        pass\n\n    def collect_timeout(self, api_data):\n        if \"timeout\" in api_data[\"controller\"]:\n            self.others[\"timeout\"] = int(api_data[\"controller\"][\"timeout\"])\n        else:\n            self.others[\"timeout\"] = None\n\n    def collect_allow_redirects(self, api_data):\n        pass\n\n    def collect_hooks(self, api_data):\n        pass\n\n    def collect_cert(self, api_data):\n        pass\n\n    def collect_assertions(self, api_data):\n        self.collect_context(api_data, 'assertions')\n\n    def collect_relations(self, api_data):\n        self.collect_context(api_data, 'relations')\n\n    def collect(self, api_data):\n        self.collect_id(api_data)\n        self.collect_name(api_data)\n        self.collect_method(api_data)\n        self.collect_url(api_data)\n        self.collect_path(api_data)\n\n        self.collect_controller(api_data)\n\n        self.collect_headers(api_data)\n        self.collect_cookies(api_data)\n        self.collect_proxies(api_data)\n\n        self.collect_query(api_data)\n        self.collect_body(api_data)\n\n        self.collect_verify(api_data)\n        self.collect_stream(api_data)\n        self.collect_auth(api_data)\n        self.collect_timeout(api_data)\n        self.collect_allow_redirects(api_data)\n        self.collect_hooks(api_data)\n        self.collect_cert(api_data)\n\n        self.collect_assertions(api_data)\n        self.collect_relations(api_data)\n\n\nclass UnDefinableMethodError(Exception):\n    \"\"\"未定义请求方法\"\"\"\n\n\nclass UnDefinablePathError(Exception):\n    \"\"\"未定义请求路径\"\"\"\n\n\nclass NotExistedFieldError(Exception):\n    \"\"\"未定义必须字段\"\"\"\n\n\nclass NotExistedFileUploadType(Exception):\n    \"\"\"未定义的文件上传方式\"\"\"\n"
  },
  {
    "path": "core/api/testcase.py",
    "content": "import re\nimport sys\n\nfrom core.api.collector import ApiRequestCollector\nfrom core.template import Template\nfrom core.api.teststep import ApiTestStep, dict2str\nfrom jsonpath_ng.parser import JsonPathParser\n\nfrom tools.utils.utils import get_case_message, get_json_relation, handle_params_data\n\n\nclass ApiTestCase:\n\n    def __init__(self, test):\n        self.test = test\n        self.case_message = get_case_message(test.test_data)\n        self.session = test.session\n        self.context = test.context\n        self.id = self.case_message['caseId']\n        self.name = self.case_message['caseName']\n        setattr(test, 'test_case_name', self.case_message['caseName'])\n        setattr(test, 'test_case_desc', self.case_message['comment'])\n        self.functions = self.case_message['functions']\n        self.params = handle_params_data(self.case_message['params'])\n        self.template = Template(self.test, self.context, self.functions, self.params)\n        self.json_path_parser = JsonPathParser()\n        self.comp = re.compile(r\"\\{\\{.*?\\}\\}\")\n\n    def execute(self):\n        \"\"\"用例执行入口函数\"\"\"\n        if self.case_message['apiList'] is None:\n            raise RuntimeError(\"无法获取API相关数据, 请重试!!!\")\n        self.loop_execute(self.case_message['apiList'], \"root\")\n\n    def loop_execute(self, api_list, loop_id, step_n=0):\n        \"\"\"循环执行\"\"\"\n        while step_n < len(api_list):\n            api_data = api_list[step_n]\n            # 定义收集器\n            collector = ApiRequestCollector()\n            step = ApiTestStep(self.test, self.session, collector, self.context, self.params)\n            # 循环控制器\n            step.collector.collect_looper(api_data)\n            if len(step.collector.looper) > 0 and not (loop_id != \"root\" and step_n == 0):\n                # 非根循环 且并非循环第一个接口时才执行循环 从而避免循环套循环情况下的死循环\n                step.looper_controller(self, api_list, step_n)\n                step_n = step_n + step.collector.looper[\"num\"]  # 跳过本次循环中执行的接口\n                continue  # 母循环最后一个接口索引必须超过子循环的最后一个接口索引 否则超过母循环的接口无法执行\n            step_n += 1\n            # 定义事务\n            self.test.defineTrans(api_data['apiId'], api_data['apiName'], api_data['path'], api_data['apiDesc'])\n            # 条件控制器\n            step.collector.collect_conditions(api_data)\n            if len(step.collector.conditions) > 0:\n                result = step.condition_controller(self)\n                if result is not True:\n                    self.test.updateTransStatus(3)  # 任意条件不满足 跳过执行\n                    self.test.debugLog('[{}]接口条件控制器判断为否: {}'.format(api_data['apiName'], result))\n                    continue\n            # 收集请求主体并执行\n            step.collector.collect(api_data)\n            try:\n                # 执行前置脚本和sql\n                if step.collector.controller[\"pre\"] is not None:\n                    for pre in step.collector.controller[\"pre\"]:\n                        if pre['name'] == 'preScript':\n                            step.exec_script(pre[\"value\"])\n                        else:\n                            step.exec_sql(pre[\"value\"], self)\n                # 渲染主体\n                self.render_content(step)\n                # 执行step, 接口参数移除，接口请求，接口响应，断言操作，依赖参数提取\n                step.execute()\n                # 执行后置脚本和sql\n                if step.collector.controller[\"post\"] is not None:\n                    for post in step.collector.controller[\"post\"]:\n                        if post['name'] == 'postScript':\n                            step.exec_script(post[\"value\"])\n                        else:\n                            step.exec_sql(post[\"value\"], self)\n                # 检查step的断言结果\n                if step.assert_result['result']:\n                    self.test.debugLog('[{}]接口断言成功: {}'.format(step.collector.apiName,\n                                                                   dict2str(step.assert_result['checkMessages'])))\n                else:\n                    self.test.errorLog('[{}]接口断言失败: {}'.format(step.collector.apiName,\n                                                                   dict2str(step.assert_result['checkMessages'])))\n                    raise AssertionError(dict2str(step.assert_result['checkMessages']))\n            except Exception as e:\n                error_info = sys.exc_info()\n                if collector.controller[\"errorContinue\"].lower() == \"true\":\n                    # 失败后继续执行\n                    if issubclass(error_info[0], AssertionError):\n                        self.test.recordFailStatus(error_info)\n                    else:\n                        self.test.recordErrorStatus(error_info)\n                else:\n                    raise e\n\n    def render_looper(self, looper):\n        self.template.init(looper)\n        _looper = self.template.render()\n        if \"times\" in _looper:\n            try:\n                times = int(_looper[\"times\"])\n            except:\n                times = 1\n            _looper[\"times\"] = times\n        return _looper\n\n    def render_conditions(self, conditions):\n        self.template.init(conditions)\n        return self.template.render()\n\n    def render_sql(self, sql):\n        self.template.init(sql)\n        return self.template.render()\n\n    def render_content(self, step):\n        self.template.init(step.collector.path)\n        step.collector.path = self.template.render()\n        if step.collector.others.get('headers') is not None:\n            headers = step.collector.others.pop('headers')\n        else:\n            headers = None\n        if step.collector.others.get('params') is not None:\n            query = step.collector.others.pop('params')\n        else:\n            query = None\n        if step.collector.others.get('data') is not None:\n            body = step.collector.others.pop('data')\n            pop_key = 'data'\n        elif step.collector.others.get('json') is not None:\n            body = step.collector.others.pop('json')\n            pop_key = 'json'\n        else:\n            body = None\n            pop_key = None\n        self.template.init(step.collector.others)\n        step.collector.others = self.template.render()\n        self.template.set_help_data(step.collector.url, step.collector.path, headers, query, body)\n        if \"#{_request_query\" in str(headers).lower() or \"#{_request_body\" in str(headers).lower():\n            if \"#{_request_body\" in str(query).lower():\n                self.render_json(step, body, \"body\", pop_key)\n                self.render_json(step, query, \"query\")\n                self.render_json(step, headers, \"headers\")\n            else:\n                self.render_json(step, query, \"query\")\n                self.render_json(step, body, \"body\", pop_key)\n                self.render_json(step, headers, \"headers\")\n        else:\n            if \"#{_request_body\" in str(query).lower():\n                self.render_json(step, headers, \"headers\")\n                self.render_json(step, body, \"body\", pop_key)\n                self.render_json(step, query, \"query\")\n            else:\n                self.render_json(step, headers, \"headers\")\n                self.render_json(step, query, \"query\")\n                self.render_json(step, body, \"body\", pop_key)\n        if step.collector.assertions is not None:\n            self.template.init(step.collector.assertions)\n            step.collector.assertions = self.template.render()\n        if step.collector.relations is not None:\n            self.template.init(step.collector.relations)\n            step.collector.relations = self.template.render()\n\n    def render_json(self, step, data, name, pop_key=None):\n        if data is None:\n            return\n        if name == \"body\" and step.collector.body_type not in (\"json\", \"form-urlencoded\", \"form-data\"):\n            self.template.init(data)\n            render_value = self.template.render()\n            self.template.request_body = render_value\n        else:\n            for expr, value in get_json_relation(data, name):\n                if isinstance(value, str) and self.comp.search(value) is not None:\n                    self.template.init(value)\n                    render_value = self.template.render()\n                    if name == \"headers\":\n                        render_value = str(render_value)\n                    expression = self.json_path_parser.parse(expr)\n                    expression.update(data, render_value)\n                    if name == \"body\":\n                        self.template.request_body = data\n                    elif name == \"query\":\n                        self.template.request_query = data\n                    else:\n                        self.template.request_headers = data\n        if name == \"body\":\n            step.collector.others.setdefault(pop_key, self.template.request_body)\n        elif name == \"query\":\n            step.collector.others.setdefault(\"params\", self.template.request_query)\n        else:\n            step.collector.others.setdefault(\"headers\", self.template.request_headers)\n\n"
  },
  {
    "path": "core/api/teststep.py",
    "content": "import datetime\nimport sys\nfrom time import sleep\n\nfrom requests import request, Session\nfrom copy import deepcopy\nimport json\n\nfrom core.assertion import LMAssert\nfrom tools.utils.sql import SQLConnect\nfrom tools.utils.utils import extract, ExtractValueError, url_join\nfrom urllib.parse import urlencode\n\nREQUEST_CNAME_MAP = {\n    'headers': '请求头',\n    'proxies': '代理',\n    'cookies': 'cookies',\n    'params': '查询参数',\n    'data': '请求体',\n    'json': '请求体',\n    'files': '上传文件'\n}\n\n\nclass ApiTestStep:\n\n    def __init__(self, test, session, collector, context, params):\n        self.session = session\n        self.collector = collector\n        self.context = context\n        self.params = params\n        self.test = test\n        self.status_code = None\n        self.response_request = None\n        self.response_headers = None\n        self.response_content = None\n        self.response_content_bytes = None\n        self.response_cookies = None\n        self.assert_result = None\n        self.print = print\n\n    def execute(self):\n        try:\n            self.test.debugLog('[{}]接口执行开始'.format(self.collector.apiName))\n            request_log = '【请求信息】:<br>'\n            request_log += '{} {}<br>'.format(self.collector.method, url_join(self.collector.url, self.collector.path))\n            for key, value in self.collector.others.items():\n                if value is not None:\n                    c_key = REQUEST_CNAME_MAP[key] if key in REQUEST_CNAME_MAP else key\n                    if key == 'files':\n                        if isinstance(value, dict):\n                            request_log += '{}: {}<br>'.format(c_key, [\"文件长度%s: %s\" % (k, len(v)) for k,v in value.items()])\n                        if isinstance(value, list):\n                            request_log += '{}: {}<br>'.format(c_key, [i[1][0] for i in value])\n                    elif c_key == '请求体':\n                        request_log += '<span>{}: {}</span><br>'.format(c_key, dict2str(value))\n                    else:\n                        request_log += '{}: {}<br>'.format(c_key, dict2str(value))\n            self.test.debugLog(request_log[:-4])\n            if self.collector.body_type == \"form-urlencoded\" and 'data' in self.collector.others:\n                self.collector.others['data'] = urlencode(self.collector.others['data'])\n            if self.collector.body_type in (\"text\", \"xml\", \"html\") and 'data' in self.collector.others:\n                self.collector.others['data'] = str(self.collector.others['data']).encode(\"utf-8\")\n            if 'files' in self.collector.others and self.collector.others['files'] is not None:\n                self.pop_content_type()\n            url = url_join(self.collector.url, self.collector.path)\n            if int(self.collector.controller[\"sleepBeforeRun\"]) > 0:\n                sleep(int(self.collector.controller[\"sleepBeforeRun\"]))\n                self.test.debugLog(\"请求前等待%sS\" % int(self.collector.controller[\"sleepBeforeRun\"]))\n            start_time = datetime.datetime.now()\n            if self.collector.controller[\"useSession\"].lower() == 'true' and self.collector.controller[\"saveSession\"].lower() == \"true\":\n                res = self.session.session.request(self.collector.method, url, **self.collector.others)\n            elif self.collector.controller[\"useSession\"].lower() == \"true\":\n                session = deepcopy(self.session.session)\n                res = session.request(self.collector.method, url, **self.collector.others)\n            elif self.collector.controller[\"saveSession\"].lower() == \"true\":\n                session = Session()\n                res = session.request(self.collector.method, url, **self.collector.others)\n                self.session.session = session\n            else:\n                res = request(self.collector.method, url, **self.collector.others)\n            end_time = datetime.datetime.now()\n            self.response_request = res.request\n            self.test.recordTransDuring(int((end_time-start_time).microseconds/1000))\n            self.save_response(res)\n            response_log = '【响应信息】:<br>'\n            response_log += '响应码: {}<br>'.format(self.status_code)\n            response_log += '响应头: {}<br>'.format(dict2str(self.response_headers))\n            if 'content-disposition' not in [key.lower() for key in self.response_headers.keys()]:\n                response_text = '<b>响应体: {}</b>'.format(dict2str(self.response_content))\n            else:\n                response_text = '<b>响应体: 文件内容暂不展示, 长度{}</b>'.format(len(self.response_content_bytes))\n            response_log += response_text\n            self.test.debugLog(response_log)\n            # 断言\n            self.check()\n            # 关联参数\n            self.extract_depend_params()\n        finally:\n            self.test.debugLog('[{}]接口执行结束'.format(self.collector.apiName))\n            if int(self.collector.controller[\"sleepAfterRun\"]) > 0:\n                sleep(int(self.collector.controller[\"sleepAfterRun\"]))\n                self.test.debugLog(\"请求后等待%sS\" % int(self.collector.controller[\"sleepAfterRun\"]))\n\n    def looper_controller(self, case, api_list, step_n):\n        \"\"\"循环控制器\"\"\"\n        if \"type\" in self.collector.looper and self.collector.looper[\"type\"] == \"WHILE\":\n            # while循环 且兼容之前只有for循环\n            loop_start_time = datetime.datetime.now()\n            while self.collector.looper[\"timeout\"] == 0 or (datetime.datetime.now() - loop_start_time).seconds * 1000 \\\n                    < self.collector.looper[\"timeout\"]:     # timeout为0时可能会死循环 慎重选择\n                # 渲染循环控制控制器 每次循环都需要渲染\n                _looper = case.render_looper(self.collector.looper)\n                result, _ = LMAssert(_looper['assertion'], _looper['target'], _looper['expect']).compare()\n                if not result:\n                    break\n                _api_list = api_list[step_n: (step_n + _looper[\"num\"])]\n                case.loop_execute(_api_list, api_list[step_n][\"apiId\"])\n        else:\n            # 渲染循环控制控制器 for只需渲染一次\n            _looper = case.render_looper(self.collector.looper)\n            for index in range(_looper[\"times\"]):  # 本次循环次数\n                self.context[_looper[\"indexName\"]] = index  # 给循环索引赋值第几次循环 母循环和子循环的索引名不应一样\n                _api_list = api_list[step_n: (step_n + _looper[\"num\"])]\n                case.loop_execute(_api_list, api_list[step_n][\"apiId\"])\n\n    def condition_controller(self, case):\n        \"\"\"条件控制器\"\"\"\n        _conditions = case.render_conditions(self.collector.conditions)\n        for condition in _conditions:\n            try:\n                result, msg = LMAssert(condition['assertion'], condition['target'], condition['expect']).compare()\n                if not result:\n                    return msg\n            except Exception as e:\n                return str(e)\n        else:\n            return True\n\n    def exec_script(self, code):\n        \"\"\"执行前后置脚本\"\"\"\n        def print(*args, sep=' ', end='\\n', file=None, flush=False):\n            if file is None or file in (sys.stdout, sys.stderr):\n                file = self.test.stdout_buffer\n            self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n        def sys_put(name, val, ps=False):\n            if ps:  # 默认给关联参数赋值，只有多传入true时才会给公参赋值\n                self.params[name] = val\n            else:\n                self.context[name] = val\n\n        def sys_get(name):\n            if name in self.context:   # 优先从公参中取值\n                return self.context[name]\n            elif name in self.params:\n                return self.params[name]\n            else:\n                raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n        names = locals()\n        names[\"res_request\"] = self.response_request\n        names[\"res_code\"] = self.status_code\n        names[\"res_header\"] = self.response_headers\n        names[\"res_data\"] = self.response_content\n        names[\"res_cookies\"] = self.response_cookies\n        names[\"res_bytes\"] = self.response_content_bytes\n        exec(code)\n\n    def exec_sql(self, sql, case):\n        \"\"\"执行前后置sql\"\"\"\n        if sql == \"{}\":\n            return\n        sql = json.loads(case.render_sql(sql))\n        if \"host\" not in sql[\"db\"]:\n            raise KeyError(\"获取数据库连接信息失败 请检查配置\")\n        conn = SQLConnect(**sql[\"db\"])\n        if sql[\"sqlType\"] != \"query\":\n            conn.exec(sql[\"sqlText\"])\n        else:\n            results = conn.query(sql[\"sqlText\"])\n            names = sql[\"names\"].split(\",\")  # name数量可以比结果数量段，但不能长，不能会indexError\n            for j, n in enumerate(names):\n                if len(results) == 0:\n                    self.context[n] = []  # 如果查询结果为空 则变量保存为空数组\n                    continue\n                if j >= len(results):\n                    raise IndexError(\"变量数错误, 请检查变量数配置是否与查询语句一致，当前查询结果: <br>{}\".format(results))\n                self.context[n] = results[j]  # 保存变量到变量空间\n\n    def save_response(self, res):\n        \"\"\"保存响应结果\"\"\"\n        self.status_code = res.status_code\n        self.response_headers = dict(res.headers)\n        self.response_content_bytes = res.content\n        s = ''\n        for key, value in res.cookies.items():\n            s += '{}={};'.format(key, value)\n        self.response_cookies = s[:-1]\n        try:\n            self.response_content = res.json()\n        except Exception:\n            self.response_content = res.text\n\n    def extract_depend_params(self):\n        \"\"\"关联参数\"\"\"\n        if self.collector.relations is not None:\n            for items in self.collector.relations:\n                if items['expression'].strip() == '$':\n                    value = self.response_content_bytes\n                elif items['expression'].strip().lower() in ['cookie', 'cookies']:\n                    value = self.response_cookies\n                else:\n                    if items['from'] == 'resHeader':\n                        data = self.response_headers\n                    elif items['from'] == 'resBody':\n                        data = self.response_content\n                    elif items['from'] == 'reqHeader':\n                        data = self.collector.others['headers']\n                    elif items['from'] == 'reqQuery':\n                        data = self.collector.others['params']\n                    elif items['from'] == 'reqBody':\n                        if self.collector.body_type == \"json\":\n                            data = self.collector.others['json']\n                        else:\n                            data = self.collector.others['data']\n                    else:\n                        raise ExtractValueError('无法从{}位置提取依赖参数'.format(items['from']))\n                    value = extract(items['method'], data, items['expression'])\n                key = items['name']\n                self.context[key] = value\n\n    def check(self):\n        \"\"\"断言\"\"\"\n        check_messages = list()\n        if self.collector.assertions is not None:\n            results = list()\n            for items in self.collector.assertions:\n                try:\n                    if items['from'] == 'resCode':\n                        actual = self.status_code\n                    elif items['from'] == 'resHeader':\n                        actual = extract(items['method'], self.response_headers, items['expression'])\n                    elif items['from'] == 'resBody':\n                        actual = extract(items['method'], self.response_content, items['expression'])\n                    else:\n                        raise ExtractValueError('无法在{}位置进行断言'.format(items['from']))\n                    result, msg = LMAssert(items['assertion'], actual, items['expect']).compare()\n                except ExtractValueError as e:\n                    result = False\n                    msg = '接口响应失败或{}'.format(str(e))\n                results.append(result)\n                check_messages.append(msg)\n                if not result:\n                    break\n            final_result = all(results)\n        else:\n            final_result, msg = LMAssert('相等', self.status_code, str(200)).compare()\n            check_messages.append(msg)\n        self.assert_result = {\n            'apiId': self.collector.apiId,\n            'apiName': self.collector.apiName,\n            'result': final_result,\n            'checkMessages': check_messages\n        }\n\n    def pop_content_type(self):\n        if self.collector.others['headers'] is None:\n            return\n        pop_key = None\n        for key, value in self.collector.others['headers'].items():\n            if key.lower() == 'content-type':\n                pop_key = key\n                break\n        if pop_key is not None:\n            self.collector.others['headers'].pop(pop_key)\n\n\ndef dict2str(data):\n    if not isinstance(data, str):\n        return str(data)\n    else:\n        return data\n\n\nclass RemoveParamError(Exception):\n    \"\"\"参数移除错误\"\"\"\n\n\nclass AssertRelationError(Exception):\n    \"\"\"断言关系错误\"\"\"\n"
  },
  {
    "path": "core/app/collector.py",
    "content": "import json\n\n\nclass AppOperationCollector:\n\n    def __init__(self):\n        self.id = None\n        self.opt_type = None\n        self.opt_system = None\n        self.opt_name = None\n        self.opt_trans = None\n        self.opt_element = None\n        self.opt_data = None\n        self.opt_code = None\n\n    @staticmethod\n    def __parse(ui_data: dict, name):\n        if name not in ui_data:\n            return None\n        return ui_data.get(name)\n\n    def collect_id(self, ui_data):\n        self.id = AppOperationCollector.__parse(ui_data, \"operationId\")\n\n    def collect_opt_type(self, ui_data):\n        self.opt_type = AppOperationCollector.__parse(ui_data, \"operationType\")\n\n    def collect_opt_system(self, ui_data):\n        self.opt_system = AppOperationCollector.__parse(ui_data, \"operationSystem\")\n\n    def collect_opt_name(self, ui_data):\n        self.opt_name = AppOperationCollector.__parse(ui_data, \"operationName\")\n\n    def collect_opt_trans(self, ui_data):\n        self.opt_trans = AppOperationCollector.__parse(ui_data, \"operationTrans\")\n\n    def collect_opt_code(self, ui_data):\n        self.opt_code = AppOperationCollector.__parse(ui_data, \"operationCode\")\n\n    def collect_opt_element(self, ui_data):\n        opt_element = AppOperationCollector.__parse(ui_data, \"operationElement\")\n        if opt_element is None or len(opt_element) == 0:\n            self.opt_element = None\n        else:\n            elements = {}\n            for name, element in opt_element.items():\n                props = {}\n                if element[\"by\"].lower() == \"prop\":\n                    for prop in json.loads(element[\"expression\"]):\n                        props[prop[\"propName\"]] = prop[\"propValue\"]\n                elif element[\"by\"].lower() == \"pred\":\n                    props[\"predicate\"] = element[\"expression\"]\n                elif element[\"by\"].lower() == \"class\":\n                    props[\"classChain\"] = element[\"expression\"]\n                else:\n                    props[element[\"by\"].lower()] = element[\"expression\"]\n                elements[name] = props\n            self.opt_element = elements\n\n    def collect_opt_data(self, ui_data):\n        opt_data = AppOperationCollector.__parse(ui_data, \"operationData\")\n        if opt_data is None or len(opt_data) == 0:\n            self.opt_data = None\n        else:\n            self.opt_data = opt_data\n\n    def collect(self, ui_data):\n        self.collect_id(ui_data)\n        self.collect_opt_type(ui_data)\n        self.collect_opt_system(ui_data)\n        self.collect_opt_name(ui_data)\n        self.collect_opt_trans(ui_data)\n        self.collect_opt_element(ui_data)\n        self.collect_opt_data(ui_data)\n        self.collect_opt_code(ui_data)\n\n"
  },
  {
    "path": "core/app/device/__init__.py",
    "content": "from typing import Optional\nfrom uiautomator2 import Device\nfrom wda import Client, AlertAction, BaseClient\n\n\nclass AndroidDriver(Device):\n    \"\"\"安卓设备\"\"\"\n    def __call__(self, **kwargs):\n        if len(kwargs) == 1 and \"xpath\" in kwargs:\n            return self.xpath(kwargs[\"xpath\"])\n        else:\n            return Device.__call__(self, **kwargs)\n\n    def find_element(self, **kwargs):\n        if len(kwargs) == 1 and \"xpath\" in kwargs:\n            return self.xpath(kwargs[\"xpath\"])\n        else:\n            return Device.__call__(self, **kwargs)\n\n\nclass AppleDevice(Client):\n    \"\"\"苹果设备\"\"\"\n    def session(self,\n                bundle_id=None,\n                arguments: Optional[list] = None,\n                environment: Optional[dict] = None,\n                alert_action: Optional[AlertAction] = None):\n        setattr(Client, 'find_element', AppleDevice.find_element)\n        client = BaseClient.session(self, bundle_id, arguments, environment, alert_action)\n        return client\n\n    def find_element(self, **kwargs):\n        return BaseClient.__call__(self, **kwargs)\n\n\ndef connect_device(system: str, url: str):\n    if system.lower() == \"android\":\n        return AndroidDriver(url)\n    else:\n        return AppleDevice(url)\n\n\nclass Operation(object):\n    def __init__(self, test, device):\n        self.device = device\n        self.test = test\n        self.print = print\n\n    def find_element(self, ele):\n        \"\"\"查找单个元素\"\"\"\n        try:\n            element = self.device.find_element(**ele)\n            self.test.debugLog(\"定位元素: %s\" % str(ele))\n            return element\n        except Exception as e:\n            self.test.errorLog(\"定位元素出错: %s\" % str(ele))\n            raise e\n\n\nclass ElementNotFoundError(Exception):\n    \"\"\"元素获取失败\"\"\"\n\n\nclass ElementNotDisappearError(Exception):\n    \"\"\"元素消失失败\"\"\"\n\n"
  },
  {
    "path": "core/app/device/assertionOpt.py",
    "content": "import sys\nfrom uiautomator2 import UiObjectNotFoundError\nfrom core.assertion import LMAssert\nfrom core.app.device import Operation\n\n\nclass Assertion(Operation):\n    \"\"\"断言类操作\"\"\"\n\n    def assert_ele_exists(self, element, assertion, expect):\n        \"\"\"断言元素存在\"\"\"\n        try:\n            actual = self.find_element(element).exists\n            self.test.debugLog(\"成功获取元素exists:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素exists\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_text(self, system, element, assertion, expect):\n        \"\"\"断言元素文本\"\"\"\n        try:\n            if system == \"android\":\n                actual = self.find_element(element).get_text()\n            else:\n                actual = self.find_element(element).text\n            self.test.debugLog(\"成功获取元素text:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素text\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_attribute(self, element, attribute, assertion, expect):\n        \"\"\"断言元素属性\"\"\"\n        try:\n            actual = self.find_element(element).info[attribute]\n            self.test.debugLog(\"成功获取元素%s属性:%s\" % (attribute, str(actual)))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素%s属性\" % attribute)\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_center(self, system, element, assertion, expect):\n        \"\"\"断言元素位置\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = (x, y)\n            else:\n                x, y = self.find_element(element).bounds.center\n                actual = (x, y)\n            self.test.debugLog(\"成功获取元素位置:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素位置\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, str(actual), expect).compare()\n            return result, msg\n\n    def assert_ele_x(self, system, element, assertion, expect):\n        \"\"\"断言元素X坐标\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = x\n            else:\n                x, y = self.find_element(element).bounds.center\n                actual = x\n            self.test.debugLog(\"成功获取元素X坐标:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素X坐标\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_y(self, system, element, assertion, expect):\n        \"\"\"断言元素Y坐标\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = y\n            else:\n                x, y = self.find_element(element).bounds.center\n                actual = y\n            self.test.debugLog(\"成功获取元素Y坐标:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素Y坐标\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_alert_exists(self, assertion, expect):\n        \"\"\"断言弹框存在 IOS专属\"\"\"\n        try:\n            actual = self.device.alert.exists\n            self.test.debugLog(\"成功获取弹框exists:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取弹框exists\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_alert_text(self, assertion, expect):\n        \"\"\"断言弹框文本 IOS专属\"\"\"\n        try:\n            actual = self.device.alert.text\n            self.test.debugLog(\"成功获取弹框文本:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取弹框文本\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"device\"] = self.device\n        names[\"test\"] = self.test\n        try:\n            \"\"\"断言操作需要返回被断言的值 以sys_return(value)返回\"\"\"\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_return(res):\n                names[\"_exec_result\"] = res\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except UiObjectNotFoundError as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n        else:\n            result, msg = LMAssert(kwargs[\"data\"][\"assertion\"], names[\"_exec_result\"], kwargs[\"data\"][\"expect\"]).compare()\n            return result, msg\n\n"
  },
  {
    "path": "core/app/device/conditionOpt.py",
    "content": "import sys\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom core.assertion import LMAssert\nfrom core.app.device import Operation\n\n\nclass Condition(Operation):\n    \"\"\"条件类操作\"\"\"\n\n    def condition_ele_exists(self, element, assertion, expect):\n        \"\"\"判断元素存在\"\"\"\n        try:\n            actual = self.find_element(element).exists\n            self.test.debugLog(\"成功获取元素exists:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素exists\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_text(self, system, element, assertion, expect):\n        \"\"\"判断元素文本\"\"\"\n        try:\n            if system == \"android\":\n                actual = self.find_element(element).get_text()\n            else:\n                actual = self.find_element(element).text\n            self.test.debugLog(\"成功获取元素text:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素text\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_attribute(self, element, attribute, assertion, expect):\n        \"\"\"判断元素属性\"\"\"\n        try:\n            actual = self.find_element(element).info[attribute]\n            self.test.debugLog(\"成功获取元素%s属性:%s\" % (attribute, str(actual)))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素%s属性\" % attribute)\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_center(self, system, element, assertion, expect):\n        \"\"\"判断元素位置\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = (x, y)\n            else:\n                size = self.find_element(element).bounds\n                actual = (size.x, size.y)\n            self.test.debugLog(\"成功获取元素位置:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素位置\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, str(actual), expect).compare()\n            return result, msg\n\n    def condition_ele_x(self, system, element, assertion, expect):\n        \"\"\"判断元素X坐标\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = x\n            else:\n                x, y = self.find_element(element).bounds.center\n                actual = x\n            self.test.debugLog(\"成功获取元素X坐标:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素X坐标\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_y(self, system, element, assertion, expect):\n        \"\"\"判断元素Y坐标\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = y\n            else:\n                x, y = self.find_element(element).bounds.center\n                actual = y\n            self.test.debugLog(\"成功获取元素Y坐标:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素Y坐标\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_alert_exists(self, assertion, expect):\n        \"\"\"判断弹框存在 IOS专属\"\"\"\n        try:\n            actual = self.device.alert.exists\n            self.test.debugLog(\"成功获取弹框exists:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取弹框exists\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_alert_text(self, assertion, expect):\n        \"\"\"判断弹框文本 IOS专属\"\"\"\n        try:\n            actual = self.device.alert.text\n            self.test.debugLog(\"成功获取弹框文本:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取弹框文本\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"device\"] = self.device\n        names[\"test\"] = self.test\n        try:\n            \"\"\"条件操作需要返回被判断的值 以sys_return(value)返回\"\"\"\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_return(res):\n                names[\"_exec_result\"] = res\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except UiObjectNotFoundError as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n        else:\n            result, msg = LMAssert(kwargs[\"data\"][\"assertion\"], names[\"_exec_result\"], kwargs[\"data\"][\"expect\"]).compare()\n            return result, msg\n\n"
  },
  {
    "path": "core/app/device/relationOpt.py",
    "content": "import sys\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom core.app.device import Operation\n\n\nclass Relation(Operation):\n    \"\"\"关联类操作\"\"\"\n    def get_window_size(self, system, save_name):\n        \"\"\"提取屏幕尺寸\"\"\"\n        try:\n            if system == \"android\":\n                w, h = self.device.window_size()\n                actual = (w, h)\n            else:\n                size = self.device.window_size()\n                actual = (size.width, size.height)\n            self.test.debugLog(\"成功获取屏幕尺寸:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取屏幕尺寸\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_window_width(self, system, save_name):\n        \"\"\"提取屏幕宽度\"\"\"\n        try:\n            if system == \"android\":\n                w, h = self.device.window_size()\n                actual = w\n            else:\n                size = self.device.window_size()\n                actual = size.width\n            self.test.debugLog(\"成功获取屏幕宽度:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取屏幕宽度\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_window_height(self, system, save_name):\n        \"\"\"提取屏幕高度\"\"\"\n        try:\n            if system == \"android\":\n                w, h = self.device.window_size()\n                actual = h\n            else:\n                size = self.device.window_size()\n                actual = size.height\n            self.test.debugLog(\"成功获取屏幕高度:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取屏幕高度\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_text(self, system, element, save_name):\n        \"\"\"提取元素文本\"\"\"\n        try:\n            if system == \"android\":\n                actual = self.find_element(element).get_text()\n            else:\n                actual = self.find_element(element).text\n            self.test.debugLog(\"成功获取元素文本:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素文本\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_center(self, system, element, save_name):\n        \"\"\"提取元素位置\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = (x, y)\n            else:\n                x, y = self.find_element(element).bounds.center\n                actual = (x, y)\n            self.test.debugLog(\"成功获取元素位置:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素位置\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_x(self, system, element, save_name):\n        \"\"\"提取元素X坐标\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = x\n            else:\n                x, y = self.find_element(element).bounds.center\n                actual = x\n            self.test.debugLog(\"成功获取元素X坐标:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素X坐标\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_y(self, system, element, save_name):\n        \"\"\"提取元素Y坐标\"\"\"\n        try:\n            if system == \"android\":\n                x, y = self.find_element(element).center()\n                actual = y\n            else:\n                x, y = self.find_element(element).bounds.center\n                actual = y\n            self.test.debugLog(\"成功获取元素Y坐标:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素Y坐标\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_alert_text(self, save_name):\n        \"\"\"提取弹框文本 IOS专属\"\"\"\n        try:\n            actual = self.device.alert.text\n            self.test.debugLog(\"成功获取弹框文本:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取弹框文本\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"device\"] = self.device\n        names[\"test\"] = self.test\n        try:\n            \"\"\"关联操作需要返回被关联的值 以sys_return(value)返回\"\"\"\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_return(res):\n                names[\"_exec_result\"] = res\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except UiObjectNotFoundError as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n        else:\n            self.test.context[kwargs[\"data\"][\"save_name\"]] = names[\"_exec_result\"]\n\n"
  },
  {
    "path": "core/app/device/scenarioOpt.py",
    "content": "import sys\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom core.app.device import Operation\n\n\nclass Scenario(Operation):\n    \"\"\"场景类操作\"\"\"\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"device\"] = self.device\n        names[\"test\"] = self.test\n        try:\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except UiObjectNotFoundError as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n"
  },
  {
    "path": "core/app/device/systemOpt.py",
    "content": "import sys\nfrom time import sleep\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom wda import WDAElementNotFoundError\nfrom core.app.device import Operation\n\n\nclass System(Operation):\n    \"\"\"系统操作\"\"\"\n\n    def start_app(self, app_id):\n        \"\"\"启动应用\"\"\"\n        try:\n            self.device.app_start(app_id)\n            self.test.debugLog(\"成功执行启动应用\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行关闭应用\")\n            raise e\n\n    def close_app(self, app_id):\n        \"\"\"关闭应用\"\"\"\n        try:\n            self.device.app_stop(app_id)\n            self.test.debugLog(\"成功执行关闭应用\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行关闭应用\")\n            raise e\n\n    def swipe_left(self, system):\n        \"\"\"左滑\"\"\"\n        try:\n            if system == \"android\":\n                self.device.swipe_ext(\"left\")\n            else:\n                self.device.swipe_left()\n            self.test.debugLog(\"成功执行左滑\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行左滑\")\n            raise e\n\n    def swipe_right(self, system):\n        \"\"\"右滑\"\"\"\n        try:\n            if system == \"android\":\n                self.device.swipe_ext(\"right\")\n            else:\n                self.device.swipe_right()\n            self.test.debugLog(\"成功执行右滑\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行右滑\")\n            raise e\n\n    def swipe_up(self, system):\n        \"\"\"上滑\"\"\"\n        try:\n            if system == \"android\":\n                self.device.swipe_ext(\"up\")\n            else:\n                self.device.swipe_up()\n            self.test.debugLog(\"成功执行上滑\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行上滑\")\n            raise e\n\n    def swipe_down(self, system):\n        \"\"\"下滑\"\"\"\n        try:\n            if system == \"android\":\n                self.device.swipe_ext(\"down\")\n            else:\n                self.device.swipe_down()\n            self.test.debugLog(\"成功执行下滑\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行下滑\")\n            raise e\n\n    def home(self, system):\n        \"\"\"系统首页\"\"\"\n        try:\n            if system == \"android\":\n                self.device.keyevent(\"home\")\n            else:\n                self.device.home()\n            self.test.debugLog(\"成功执行返回系统首页\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行返回系统首页\")\n            raise e\n\n    def back(self):\n        \"\"\"系统返回 安卓专用\"\"\"\n        try:\n            self.device.keyevent(\"back\")\n            self.test.debugLog(\"成功执行返回\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行返回\")\n            raise e\n\n    def press(self, keycode):\n        \"\"\"系统按键\"\"\"\n        try:\n            self.device.press(keycode)\n            self.test.debugLog(\"成功执行按下系统键位: %s\" % keycode)\n        except Exception as e:\n            self.test.errorLog(\"无法执行按下系统键位: %s\" % keycode)\n            raise e\n\n    def screenshot(self, name):\n        \"\"\"屏幕截图\"\"\"\n        try:\n            screenshot = self.device.screenshot(format='raw')\n            self.test.saveScreenShot(name, screenshot)\n            self.test.debugLog(\"成功执行屏幕截图\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行屏幕截图\")\n            raise e\n\n    def screen_on(self, system):\n        \"\"\"亮屏\"\"\"\n        try:\n            if system == \"android\":\n                self.device.screen_on()\n            else:\n                self.device.unlock()\n            self.test.debugLog(\"成功执行亮屏\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行亮屏\")\n            raise e\n\n    def screen_off(self, system):\n        \"\"\"息屏\"\"\"\n        try:\n            if system == \"android\":\n                self.device.screen_off()\n            else:\n                self.device.lock()\n            self.test.debugLog(\"成功执行息屏\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行息屏\")\n            raise e\n\n    def sleep(self, second):\n        \"\"\"强制等待\"\"\"\n        try:\n            sleep(second)\n            self.test.debugLog(\"成功执行sleep %ds\" % second)\n        except Exception as e:\n            self.test.errorLog(\"无法执行sleep %ds\" % second)\n            raise e\n\n    def implicitly_wait(self, second):\n        \"\"\"隐式等待\"\"\"\n        try:\n            self.device.implicitly_wait(second)\n            self.test.debugLog(\"成功执行implicitly wait %ds\" % second)\n        except Exception as e:\n            self.test.errorLog(\"无法执行implicitly wait %ds\" % second)\n            raise e\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"device\"] = self.device\n        names[\"test\"] = self.test\n        try:\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except UiObjectNotFoundError as e:\n            raise e\n        except WDAElementNotFoundError as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n"
  },
  {
    "path": "core/app/device/viewOpt.py",
    "content": "import sys\n\nfrom uiautomator2 import UiObjectNotFoundError\nfrom uiautomator2.xpath import XPath\nfrom wda import WDAElementNotFoundError\nfrom core.app.device import Operation, ElementNotFoundError, ElementNotDisappearError\n\n\nclass View(Operation):\n    \"\"\"视图类操作\"\"\"\n\n    def click(self, element):\n        \"\"\"单击\"\"\"\n        try:\n            self.find_element(element).click_exists(timeout=3)\n            self.test.debugLog(\"成功单击\")\n        except Exception as e:\n            self.test.errorLog(\"无法单击\")\n            raise e\n\n    def double_click(self, system, element):\n        \"\"\"双击\"\"\"\n        try:\n            if system == \"android\":\n                self.device.double_click(*self.find_element(element).center())\n            else:\n                self.device.double_tap(*self.find_element(element).center())\n            self.test.debugLog(\"成功双击\")\n        except Exception as e:\n            self.test.errorLog(\"无法双击\")\n            raise e\n\n    def long_click(self, system, element, second):\n        \"\"\"长按\"\"\"\n        try:\n            if system == \"android\":\n                if \"xpath\" in element:\n                    self.find_element(element).long_click()\n                else:\n                    self.find_element(element).long_click(second)\n            else:\n                self.find_element(element).tap_hold(second)\n            self.test.debugLog(\"成功长按%sS\" % str(second))\n        except Exception as e:\n            self.test.errorLog(\"无法长按%sS\" % str(second))\n            raise e\n\n    def click_coord(self, x, y):\n        \"\"\"坐标单击 百分比或坐标值\"\"\"\n        try:\n            self.device.click(x, y)\n            self.test.debugLog(\"成功坐标单击\")\n        except Exception as e:\n            self.test.errorLog(\"无法坐标单击\")\n            raise e\n\n    def double_click_coord(self, system, x, y):\n        \"\"\"坐标双击 百分比或坐标值\"\"\"\n        try:\n            if system == \"android\":\n                self.device.double_click(x, y)\n            else:\n                self.device.double_tap(x, y)\n            self.test.debugLog(\"成功坐标双击\")\n        except Exception as e:\n            self.test.errorLog(\"无法坐标双击\")\n            raise e\n\n    def long_click_coord(self, system, x, y, second):\n        \"\"\"坐标长按 百分比或坐标值\"\"\"\n        try:\n            if system == \"android\":\n                self.device.long_click(x, y, second)\n            else:\n                self.device.tap_hold(x, y, second)\n            self.test.debugLog(\"成功坐标长按%sS\" % str(second))\n        except Exception as e:\n            self.test.errorLog(\"无法坐标长按%sS\" % str(second))\n            raise e\n\n    def swipe(self, system, fx, fy, tx, ty, duration=None):\n        \"\"\"坐标滑动 百分比或坐标值\"\"\"\n        try:\n            if system == \"android\":\n                if duration == \"\":\n                    duration = None\n                self.device.swipe(fx, fy, tx, ty, duration)\n            else:\n                if duration == \"\" or duration is None:\n                    duration = 0\n                self.device.swipe(fx, fy, tx, ty, duration)\n            self.test.debugLog(\"成功执行滑动\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行滑动\")\n            raise e\n\n    def input_text(self, element, text):\n        \"\"\"输入\"\"\"\n        try:\n            self.find_element(element).set_text(text)\n            self.test.debugLog(\"成功输入%s\" % str(text))\n        except Exception as e:\n            self.test.errorLog(\"无法输入%s\" % str(text))\n            raise e\n\n    def clear_text(self, system, element):\n        \"\"\"清空\"\"\"\n        try:\n            ele = self.find_element(element)\n            if system == \"android\" and len(element) == 1 and \"xpath\" in element:\n                xe = ele.get()\n                ele._d.set_fastinput_ime()\n                xe.click()\n                ele._parent._d.set_fastinput_ime()\n                ele._parent._d.clear_text()\n            else:\n                ele.clear_text()\n            self.test.debugLog(\"成功清空\")\n        except Exception as e:\n            self.test.errorLog(\"无法清空\")\n            raise e\n\n    def scroll_to_ele(self, system, element, direction):\n        \"\"\"滑动到元素出现\"\"\"\n        try:\n            if system == \"android\":\n                if \"xpath\" in element:\n                    XPath(self.device).scroll_to(element[\"xpath\"], direction)\n                elif direction == \"up\":\n                    self.device(scrollable=True).forward.to(**element)\n                elif direction == \"down\":\n                    self.device(scrollable=True).backward.to(**element)\n                elif direction == \"left\":\n                    self.device(scrollable=True).horiz.forward.to(**element)\n                else:\n                    self.device(scrollable=True).horiz.backward.to(**element)\n            else:\n                self.find_element(element).scroll(direction)\n            self.test.debugLog(\"成功滑动到元素出现\")\n        except Exception as e:\n            self.test.errorLog(\"无法滑动到元素出现\")\n            raise e\n\n    def pinch_in(self, system, element):\n        \"\"\"缩小 安卓仅支持属性定位\"\"\"\n        try:\n            if system == \"android\":\n                self.find_element(element).pinch_in()\n            else:\n                self.find_element(element).pinch(0.5, -1)\n            self.test.debugLog(\"成功缩小\")\n        except Exception as e:\n            self.test.errorLog(\"无法缩小\")\n            raise e\n\n    def pinch_out(self, system, element):\n        \"\"\"放大 安卓仅支持属性定位\"\"\"\n        try:\n            if system == \"android\":\n                self.find_element(element).pinch_out()\n            else:\n                self.find_element(element).pinch(2, 1)\n            self.test.debugLog(\"成功放大\")\n        except Exception as e:\n            self.test.errorLog(\"无法放大\")\n            raise e\n\n    def wait(self, element, second):\n        \"\"\"等待元素出现\"\"\"\n        try:\n            if self.find_element(element).wait(timeout=second):\n                self.test.debugLog(\"成功等待元素出现\")\n            else:\n                self.test.errorLog(\"等待元素出现失败 元素不存在\")\n                raise ElementNotFoundError(\"element not exists\")\n        except ElementNotFoundError as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法等待元素出现\")\n            raise e\n\n    def wait_gone(self, system, element, second):\n        \"\"\"等待元素消失\"\"\"\n        try:\n            if system == \"android\":\n                res = self.find_element(element).wait_gone(timeout=second)\n            else:\n                res = self.find_element(element).wait_gone(timeout=second, raise_error=False)\n            if res:\n                self.test.debugLog(\"成功等待元素消失\")\n            else:\n                self.test.errorLog(\"等待元素消失失败 元素仍存在\")\n                raise ElementNotDisappearError(\"element exists\")\n        except ElementNotDisappearError as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法等待元素消失\")\n            raise e\n\n    def drag_to_ele(self, start_element, end_element):\n        \"\"\"拖动到元素 安卓专属 只支持属性定位\"\"\"\n        try:\n            self.find_element(start_element).drag_to(**end_element)\n            self.test.debugLog(\"成功拖动到元素\")\n        except Exception as e:\n            self.test.errorLog(\"无法拖动到元素\")\n            raise e\n\n    def drag_to_coord(self, element, x, y):\n        \"\"\"拖动到坐标 安卓专属 只支持属性定位\"\"\"\n        try:\n            self.find_element(element).drag_to(x, y)\n            self.test.debugLog(\"成功拖动到坐标\")\n        except Exception as e:\n            self.test.errorLog(\"无法拖动到坐标\")\n            raise e\n\n    def drag_coord(self, fx, fy, tx, ty):\n        \"\"\"坐标拖动 安卓专属\"\"\"\n        try:\n            self.device.drag(fx, fy, tx, ty)\n            self.test.debugLog(\"成功坐标拖动\")\n        except Exception as e:\n            self.test.errorLog(\"无法坐标拖动\")\n            raise e\n\n    def swipe_ele(self, element, direction):\n        \"\"\"元素内滑动 安卓专属\"\"\"\n        try:\n            self.find_element(element).swipe(direction)\n            self.test.debugLog(\"成功元素内滑动\")\n        except Exception as e:\n            self.test.errorLog(\"无法元素内滑动\")\n            raise e\n\n    def alert_wait(self, second):\n        \"\"\"等待弹框出现 IOS专属\"\"\"\n        try:\n            self.device.alert.wait(second)\n            self.test.debugLog(\"成功等待弹框出现\")\n        except Exception as e:\n            self.test.errorLog(\"无法等待弹框出现\")\n            raise e\n\n    def alert_accept(self):\n        \"\"\"弹框确认 IOS专属\"\"\"\n        try:\n            self.device.alert.accept()\n            self.test.debugLog(\"成功弹框确认\")\n        except Exception as e:\n            self.test.errorLog(\"无法弹框确认\")\n            raise e\n\n    def alert_dismiss(self):\n        \"\"\"弹框取消 IOS专属\"\"\"\n        try:\n            self.device.alert.dismiss()\n            self.test.debugLog(\"成功弹框取消\")\n        except Exception as e:\n            self.test.errorLog(\"无法弹框取消\")\n            raise e\n\n    def alert_click(self, name):\n        \"\"\"弹框点击 IOS专属\"\"\"\n        try:\n            self.device.alert.click(name)\n            self.test.debugLog(\"成功弹框点击%s\" % name)\n        except Exception as e:\n            self.test.errorLog(\"无法弹框点击%s\" % name)\n            raise e\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"device\"] = self.device\n        names[\"test\"] = self.test\n        try:\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except UiObjectNotFoundError as e:\n            raise e\n        except WDAElementNotFoundError as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n"
  },
  {
    "path": "core/app/find_opt.py",
    "content": "from core.app.device.viewOpt import View\nfrom core.app.device.systemOpt import System\nfrom core.app.device.scenarioOpt import Scenario\nfrom core.app.device.assertionOpt import Assertion\nfrom core.app.device.relationOpt import Relation\nfrom core.app.device.conditionOpt import Condition\n\n\ndef find_system_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"启动应用\")\n    def start_app(test, device, **kwargs):\n        System(test, device).start_app(kwargs[\"data\"][\"appId\"])\n\n    @keywords(\"关闭应用\")\n    def close_app(test, device, **kwargs):\n        System(test, device).close_app(kwargs[\"data\"][\"appId\"])\n\n    @keywords(\"左滑\")\n    def swipe_left(test, device, **kwargs):\n        System(test, device).swipe_left(kwargs[\"system\"])\n\n    @keywords(\"右滑\")\n    def swipe_right(test, device, **kwargs):\n        System(test, device).swipe_right(kwargs[\"system\"])\n\n    @keywords(\"上滑\")\n    def swipe_up(test, device, **kwargs):\n        System(test, device).swipe_up(kwargs[\"system\"])\n\n    @keywords(\"下滑\")\n    def swipe_down(test, device, **kwargs):\n        System(test, device).swipe_down(kwargs[\"system\"])\n\n    @keywords(\"系统首页\")\n    def home(test, device, **kwargs):\n        System(test, device).home(kwargs[\"system\"])\n\n    @keywords(\"系统返回\")\n    def back(test, device, **kwargs):\n        System(test, device).back()\n\n    @keywords(\"系统按键\")\n    def press(test, device, **kwargs):\n        System(test, device).press(kwargs[\"data\"][\"keycode\"])\n\n    @keywords(\"屏幕截图\")\n    def screenshot(test, device, **kwargs):\n        System(test, device).screenshot(kwargs[\"data\"][\"name\"])\n\n    @keywords(\"亮屏\")\n    def screen_on(test, device, **kwargs):\n        System(test, device).screen_on(kwargs[\"system\"])\n\n    @keywords(\"息屏\")\n    def screen_off(test, device, **kwargs):\n        System(test, device).screen_off(kwargs[\"system\"])\n\n    @keywords(\"强制等待\")\n    def sleep(test, device, **kwargs):\n        System(test, device).sleep(kwargs[\"data\"][\"second\"])\n\n    @keywords(\"隐式等待\")\n    def implicitly_wait(test, device, **kwargs):\n        System(test, device).implicitly_wait(kwargs[\"data\"][\"second\"])\n\n    @keywords(\"自定义\")\n    def custom(test, device, **kwargs):\n        System(test, device).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_view_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n        return back\n\n    @keywords(\"单击\")\n    def click(test, device, **kwargs):\n        View(test, device).click(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"双击\")\n    def double_click(test, device, **kwargs):\n        View(test, device).double_click(kwargs[\"system\"], kwargs[\"element\"][\"element\"])\n\n    @keywords(\"长按\")\n    def long_click(test, device, **kwargs):\n        View(test, device).long_click(kwargs[\"system\"], kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"second\"])\n\n    @keywords(\"坐标单击\")\n    def click_coord(test, device, **kwargs):\n        View(test, device).click_coord(**kwargs[\"data\"])\n\n    @keywords(\"坐标双击\")\n    def double_click_coord(test, device, **kwargs):\n        View(test, device).double_click_coord(kwargs[\"system\"], **kwargs[\"data\"])\n\n    @keywords(\"坐标长按\")\n    def long_click_coord(test, device, **kwargs):\n        View(test, device).long_click_coord(kwargs[\"system\"], **kwargs[\"data\"])\n\n    @keywords(\"坐标滑动\")\n    def swipe_int(test, device, **kwargs):\n        View(test, device).swipe(kwargs[\"system\"], **kwargs[\"data\"])\n\n    @keywords(\"输入\")\n    def input_text(test, device, **kwargs):\n        View(test, device).input_text(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"text\"])\n\n    @keywords(\"清空\")\n    def input_text(test, device, **kwargs):\n        View(test, device).clear_text(kwargs[\"system\"], kwargs[\"element\"][\"element\"])\n\n    @keywords(\"滑动到元素出现\")\n    def scroll_to_ele(test, device, **kwargs):\n        View(test, device).scroll_to_ele(kwargs[\"system\"], kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"direction\"])\n\n    @keywords(\"缩小\")\n    def pinch_in(test, device, **kwargs):\n        View(test, device).pinch_in(kwargs[\"system\"], kwargs[\"element\"][\"element\"])\n\n    @keywords(\"放大\")\n    def pinch_out(test, device, **kwargs):\n        View(test, device).pinch_out(kwargs[\"system\"], kwargs[\"element\"][\"element\"])\n\n    @keywords(\"等待元素出现\")\n    def wait(test, device, **kwargs):\n        View(test, device).wait(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"second\"])\n\n    @keywords(\"等待元素消失\")\n    def wait_gone(test, device, **kwargs):\n        View(test, device).wait_gone(kwargs[\"system\"], kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"second\"])\n\n    @keywords(\"拖动到元素\")\n    def drag_to_ele(test, device, **kwargs):\n        View(test, device).drag_to_ele(kwargs[\"element\"][\"startElement\"],kwargs[\"element\"][\"endElement\"])\n\n    @keywords(\"拖动到坐标\")\n    def drag_to_coord(test, device, **kwargs):\n        View(test, device).drag_to_coord(kwargs[\"element\"][\"element\"], **kwargs[\"data\"])\n\n    @keywords(\"坐标拖动\")\n    def drag_coord(test, device, **kwargs):\n        View(test, device).drag_coord(**kwargs[\"data\"])\n\n    @keywords(\"元素内滑动\")\n    def swipe_ele(test, device, **kwargs):\n        View(test, device).swipe_ele(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"direction\"])\n\n    @keywords(\"等待弹框出现\")\n    def alert_wait(test, device, **kwargs):\n        View(test, device).alert_wait(kwargs[\"data\"][\"second\"])\n\n    @keywords(\"弹框确认\")\n    def alert_accept(test, device, **kwargs):\n        View(test, device).alert_accept()\n\n    @keywords(\"弹框取消\")\n    def alert_dismiss(test, device, **kwargs):\n        View(test, device).alert_dismiss()\n\n    @keywords(\"弹框点击\")\n    def alert_click(test, device, **kwargs):\n        View(test, device).alert_click(kwargs[\"data\"][\"name\"])\n\n    @keywords(\"自定义\")\n    def custom(test, device, **kwargs):\n        View(test, device).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_assertion_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"断言元素存在\")\n    def assert_ele_exists(test, device, **kwargs):\n        return Assertion(test, device).assert_ele_exists(kwargs[\"element\"][\"element\"],\n                                                         kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素文本\")\n    def assert_ele_text(test, device, **kwargs):\n        return Assertion(test, device).assert_ele_text(kwargs[\"system\"], kwargs[\"element\"][\"element\"],\n                                                       kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素属性\")\n    def assert_ele_attribute(test, device, **kwargs):\n        return Assertion(test, device).assert_ele_attribute(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"attribute\"],\n                                                            kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素位置\")\n    def assert_ele_center(test, device, **kwargs):\n        return Assertion(test, device).assert_ele_center(kwargs[\"system\"], kwargs[\"element\"][\"element\"],\n                                                         kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素X坐标\")\n    def assert_ele_x(test, device, **kwargs):\n        return Assertion(test, device).assert_ele_x(kwargs[\"system\"], kwargs[\"element\"][\"element\"],\n                                                    kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素Y坐标\")\n    def assert_ele_y(test, device, **kwargs):\n        return Assertion(test, device).assert_ele_y(kwargs[\"system\"], kwargs[\"element\"][\"element\"],\n                                                    kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言弹框存在\")\n    def assert_alert_exists(test, device, **kwargs):\n        return Assertion(test, device).assert_alert_exists(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言弹框文本\")\n    def assert_alert_text(test, device, **kwargs):\n        return Assertion(test, device).assert_alert_text(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"自定义\")\n    def custom(test, device, **kwargs):\n        return Assertion(test, device).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_relation_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"提取屏幕尺寸\")\n    def get_window_size(test, device, **kwargs):\n        Relation(test, device).get_window_size(kwargs[\"system\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取屏幕宽度\")\n    def get_window_width(test, device, **kwargs):\n        Relation(test, device).get_window_width(kwargs[\"system\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取屏幕高度\")\n    def get_window_height(test, device, **kwargs):\n        Relation(test, device).get_window_height(kwargs[\"system\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素文本\")\n    def get_ele_text(test, device, **kwargs):\n        Relation(test, device).get_ele_text(kwargs[\"system\"], kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素位置\")\n    def get_ele_center(test, device, **kwargs):\n        Relation(test, device).get_ele_center(kwargs[\"system\"], kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素X坐标\")\n    def get_ele_x(test, device, **kwargs):\n        Relation(test, device).get_ele_x(kwargs[\"system\"], kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素Y坐标\")\n    def get_ele_y(test, device, **kwargs):\n        Relation(test, device).get_ele_y(kwargs[\"system\"], kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取弹框文本\")\n    def get_alert_text(test, device, **kwargs):\n        Relation(test, device).get_alert_text(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"自定义\")\n    def custom(test, device, **kwargs):\n        Relation(test, device).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_condition_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"判断元素存在\")\n    def condition_ele_exists(test, device, **kwargs):\n        return Condition(test, device).condition_ele_exists(kwargs[\"element\"][\"element\"],\n                                                            kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素文本\")\n    def condition_ele_text(test, device, **kwargs):\n        return Condition(test, device).condition_ele_text(kwargs[\"system\"], kwargs[\"element\"][\"element\"],\n                                                          kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素属性\")\n    def condition_ele_attribute(test, device, **kwargs):\n        return Condition(test, device).condition_ele_attribute(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"attribute\"],\n                                                            kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素位置\")\n    def condition_ele_center(test, device, **kwargs):\n        return Condition(test, device).condition_ele_center(kwargs[\"system\"], kwargs[\"element\"][\"element\"],\n                                                            kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素X坐标\")\n    def condition_ele_x(test, device, **kwargs):\n        return Condition(test, device).condition_ele_x(kwargs[\"system\"], kwargs[\"element\"][\"element\"],\n                                                       kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素Y坐标\")\n    def condition_ele_y(test, device, **kwargs):\n        return Condition(test, device).condition_ele_y(kwargs[\"system\"], kwargs[\"element\"][\"element\"],\n                                                       kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断弹框存在\")\n    def condition_alert_exists(test, device, **kwargs):\n        return Condition(test, device).condition_alert_exists(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断弹框文本\")\n    def condition_alert_text(test, device, **kwargs):\n        return Condition(test, device).condition_alert_text(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"自定义\")\n    def custom(test, device, **kwargs):\n        return Condition(test, device).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_scenario_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"自定义\")\n    def custom(test, device, **kwargs):\n        return Scenario(test, device).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n"
  },
  {
    "path": "core/app/testcase.py",
    "content": "from core.template import Template\nfrom core.app.collector import AppOperationCollector\nfrom core.app.teststep import AppTestStep\nfrom core.app.device import connect_device\nfrom tools.utils.utils import get_case_message, handle_operation_data, handle_params_data\nimport re\n\n\nclass AppTestCase:\n    def __init__(self, test):\n        self.test = test\n        self.context = test.context\n        self.case_message = get_case_message(test.test_data)\n        self.id = self.case_message['caseId']\n        self.name = self.case_message['caseName']\n        setattr(test, 'test_case_name', self.case_message['caseName'])\n        setattr(test, 'test_case_desc', self.case_message['comment'])\n        self.functions = self.case_message['functions']\n        self.params = handle_params_data(self.case_message['params'])\n        test.common_params = self.params\n        self.device = self.before_execute()\n        self.template = Template(self.test, self.context, self.functions, self.params)\n        self.comp = re.compile(r\"\\{\\{.*?\\}\\}\")\n\n    def execute(self):\n        if self.case_message['optList'] is None:\n            self.after_execute()\n            raise RuntimeError(\"无法获取APP测试相关数据, 请重试!!!\")\n        try:\n            self.loop_execute(self.case_message['optList'], [])\n        finally:\n            self.after_execute()\n\n    def loop_execute(self, opt_list, skip_opts, step_n=0):\n        while step_n < len(opt_list):\n            opt_content = opt_list[step_n]\n            # 定义收集器\n            collector = AppOperationCollector()\n            step = AppTestStep(self.test, self.device, self.context, collector)\n            # 定义事务\n            self.test.defineTrans(opt_content[\"operationId\"], opt_content['operationTrans'],\n                                  self.get_opt_content(opt_content['operationElement']), opt_content['operationDesc'])\n            if step_n in skip_opts:\n                self.test.updateTransStatus(3)\n                self.test.debugLog('[{}]操作在条件控制之外不被执行'.format(opt_content['operationTrans']))\n                step_n += 1\n                continue\n            # 收集步骤信息\n            step.collector.collect(opt_content)\n            try:\n                if step.collector.opt_type == \"looper\":\n                    looper_step_num = step.looper_controller(self, opt_list, step_n)\n                    step_n += looper_step_num + 1\n                else:\n                    # 渲染主体\n                    self.render_content(step)\n                    step.execute()\n                    step.assert_controller()\n                    skip_opts.extend(step.condition_controller(step_n))\n                    step_n += 1\n            except Exception as e:\n                if not isinstance(e, AssertionError):\n                    self.test.saveScreenShot(opt_content['operationTrans'], self.device.screenshot(format='raw'))\n                raise e\n\n    @staticmethod\n    def get_opt_content(elements):\n        content = \"\"\n        if elements is not None:\n            for key, element in elements.items():\n                content = \"%s\\n %s: %s\" % (content, key, element[\"target\"])\n        return content\n\n    def before_execute(self):\n        if self.case_message['deviceUrl'] is None:\n            raise Exception(\"执行设备不在线 本用例执行失败\")\n        device = connect_device(self.case_message['deviceSystem'], f\"http://{self.case_message['deviceUrl']}\")\n        if self.case_message['deviceSystem'] == 'android':\n            device.healthcheck()\n            device.app_start(self.case_message['appId'], self.case_message['activity'])\n            return device\n        else:\n            device = device.session(self.case_message['appId'])\n            device._wda_url = f\"http://{self.case_message['deviceUrl']}\"\n            return device\n\n    def after_execute(self):\n        self.device.app_stop(self.case_message['appId'])\n\n    def render_looper(self, looper):\n        self.template.init(looper)\n        _looper = self.template.render()\n        for name, param in _looper.items():\n            if name != \"target\" or name != \"expect\":    # 断言实际值不作数据处理\n                _looper[name] = handle_operation_data(param[\"type\"], param[\"value\"])\n        if \"times\" in _looper:\n            try:\n                times = int(_looper[\"times\"])\n            except:\n                times = 1\n            _looper[\"times\"] = times\n        return _looper\n\n    def render_content(self, step):\n        if step.collector.opt_element is not None:\n            for name, expression in step.collector.opt_element.items():\n                if self.comp.search(str(expression)) is not None:\n                    self.template.init(expression)\n                    expression = self.template.render()\n                step.collector.opt_element[name] = expression\n        if step.collector.opt_data is not None:\n            data = {}\n            for name, param in step.collector.opt_data.items():\n                param_value = param[\"value\"]\n                if isinstance(param_value, str) and self.comp.search(param_value) is not None:\n                    self.template.init(param_value)\n                    param_value = self.template.render()\n                data[name] = handle_operation_data(param[\"type\"], param_value)\n            step.collector.opt_data = data\n\n"
  },
  {
    "path": "core/app/teststep.py",
    "content": "import sys\nfrom datetime import datetime\nfrom core.app.find_opt import *\nfrom core.assertion import LMAssert\n\n\nclass AppTestStep:\n    def __init__(self, test, device, context, collector):\n        self.test = test\n        self.device = device\n        self.context = context\n        self.collector = collector\n        self.result = None\n\n    def execute(self):\n        try:\n            self.test.debugLog('APP操作[{}]开始'.format(self.collector.opt_name))\n            opt_type = self.collector.opt_type\n            if opt_type == \"system\":\n                func = find_system_opt(self.collector.opt_name)\n            elif opt_type == \"view\":\n                func = find_view_opt(self.collector.opt_name)\n            elif opt_type == \"condition\":\n                func = find_condition_opt(self.collector.opt_name)\n            elif opt_type == \"assertion\":\n                func = find_assertion_opt(self.collector.opt_name)\n            elif opt_type == \"relation\":\n                func = find_relation_opt(self.collector.opt_name)\n            else:\n                func = find_scenario_opt(self.collector.opt_name)\n            if func is None:\n                raise NotExistedAppOperation(\"未定义操作\")\n            opt_content = {\n                \"system\": self.collector.opt_system,\n                \"trans\": self.collector.opt_trans,\n                \"code\": self.collector.opt_code,\n                \"element\": self.collector.opt_element,\n                \"data\": self.collector.opt_data\n            }\n            self.result = func(self.test, self.device, **opt_content)\n            self.log_show()\n        finally:\n            self.test.debugLog('APP操作[{}]结束'.format(self.collector.opt_name))\n\n    def looper_controller(self, case, opt_list, step_n):\n        \"\"\"循环控制器\"\"\"\n        if self.collector.opt_trans == \"While循环\":\n            loop_start_time = datetime.now()\n            timeout = int(self.collector.opt_data[\"timeout\"][\"value\"])\n            index_name = self.collector.opt_data[\"indexName\"][\"value\"]\n            steps = int(self.collector.opt_data[\"steps\"][\"value\"])\n            index = 0\n            while timeout == 0 or (datetime.now() - loop_start_time).seconds * 1000 < timeout:\n                # timeout为0时可能会死循环 慎重选择\n                self.context[index_name] = index  # 给循环索引赋值第几次循环 母循环和子循环的索引名不应一样\n                _looper = case.render_looper(self.collector.opt_data)  # 渲染循环控制控制器 每次循环都需要渲染\n                index += 1\n                result, _ = LMAssert(_looper['assertion'], _looper['target'], _looper['expect']).compare()\n                if not result:\n                    break\n                _opt_list = opt_list[step_n+1: (step_n + _looper[\"steps\"]+1)]   # 循环操作本身不参与循环 不然死循环\n                case.loop_execute(_opt_list, [])\n            return steps\n        else:\n            _looper = case.render_looper(self.collector.opt_data) # 渲染循环控制控制器 for只需渲染一次\n            for index in range(_looper[\"times\"]):  # 本次循环次数\n                self.context[_looper[\"indexName\"]] = index  # 给循环索引赋值第几次循环 母循环和子循环的索引名不应一样\n                _opt_list = opt_list[step_n+1: (step_n + _looper[\"steps\"]+1)]\n                case.loop_execute(_opt_list, [])\n            return _looper[\"steps\"]\n\n    def assert_controller(self):\n        if self.collector.opt_type == \"assertion\":\n            if self.result[0]:\n                self.test.debugLog('[{}]断言成功: {}'.format(self.collector.opt_trans,\n                                                             self.result[1]))\n            else:\n                self.test.errorLog('[{}]断言失败: {}'.format(self.collector.opt_trans,\n                                                             self.result[1]))\n                self.test.saveScreenShot(self.collector.opt_trans, self.device.screenshot(format='raw'))\n                if \"continue\" in self.collector.opt_data and self.collector.opt_data[\"continue\"] is True:\n                    try:\n                        raise AssertionError(self.result[1])\n                    except AssertionError:\n                        error_info = sys.exc_info()\n                        self.test.recordFailStatus(error_info)\n                else:\n                    raise AssertionError(self.result[1])\n\n    def condition_controller(self, current):\n        if self.collector.opt_type == \"condition\":\n            offset_true = self.collector.opt_data[\"true\"]\n            if not isinstance(offset_true, int):\n                offset_true = 0\n            offset_false = self.collector.opt_data[\"false\"]\n            if not isinstance(offset_false, int):\n                offset_false = 0\n            if self.result[0]:\n                self.test.debugLog('[{}]判断成功, 执行成功分支: {}'.format(self.collector.opt_name,\n                                                                        self.result[1]))\n                return [current + i for i in range(offset_true + 1, offset_true + offset_false + 1)]\n            else:\n                self.test.errorLog('[{}]判断失败, 执行失败分支: {}'.format(self.collector.opt_name,\n                                                                        self.result[1]))\n                return [current + i for i in range(1, offset_true + 1)]\n        return []\n\n    def log_show(self):\n        msg = \"\"\n        if self.collector.opt_element is not None:\n            for k, v in self.collector.opt_element.items():\n                msg += '元素定位: {}: {}<br>'.format(k, v)\n        if self.collector.opt_data is not None:\n            data_log = '{'\n            for k, v in self.collector.opt_data.items():\n                class_name = type(v).__name__\n                data_log += \"{}: {}, \".format(k, v)\n            if len(data_log) > 1:\n                data_log = data_log[:-2]\n            data_log += '}'\n            msg += '操作数据: {}'.format(data_log)\n        if msg != \"\":\n            msg = '操作信息: <br>' + msg\n            self.test.debugLog(msg)\n\n\nclass NotExistedAppOperation(Exception):\n    \"\"\"未定义的操作\"\"\"\n"
  },
  {
    "path": "core/assertion.py",
    "content": "# -*- coding: utf-8 -*-\nimport re\nimport ast\n\nfrom assertpy import assertpy\n\n\nclass LMAssert:\n    \"\"\"断言\"\"\"\n\n    def __init__(self, position, actual_result, expected_result):\n        self.comparator = position\n        self.actual_result = actual_result\n        self.expected_result = expected_result\n\n    def compare(self):\n        try:\n            if self.comparator in [\"equal\", \"equals\", \"相等\", \"字符相等\"]:  # 等于\n                assFailMsg = '实际值({})与预期值({}) 字符相等，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(str(self.actual_result)).is_equal_to(self.expected_result)\n            elif self.comparator in [\"equalsList\", \"数组相等\"]:  # 列表相同，包括列表顺序也相同\n                assFailMsg = '实际值({})与预期值({}) 数组相等，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2list(self.actual_result)).is_equal_to(LMAssert.str2list(self.expected_result))\n            elif self.comparator in [\"equalsDict\", \"对象相等\"]:  # 字典相同\n                assFailMsg = '实际值({})与预期值({}) 对象相等，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2dict(self.actual_result)).is_equal_to(LMAssert.str2dict(self.expected_result))\n            elif self.comparator in [\"equalsNumber\", \"数字相等\", \"数值相等\"]:  # 数字等于\n                assFailMsg = '实际值({})与预期值({}) 数值相等，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_equal_to(LMAssert.str2num(self.expected_result))\n            elif self.comparator in [\"equalIgnoreCase\", \"相等(忽略大小写)\"]:  # 忽略大小写等于\n                assFailMsg = '实际值({})与预期值({}) 相等(忽略大小写)，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(str(self.actual_result)).is_equal_to_ignoring_case(self.expected_result)\n            elif self.comparator in [\"notEqual\", \"does not equal\", \"不等于\"]:  # 不等于\n                assFailMsg = '实际值({}) 不等于 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_not_equal_to(self.expected_result)\n            elif self.comparator in [\"contains\", \"包含\"]:  # 字符串包含该字符\n                assFailMsg = '实际值({}) 包含 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.to_str(self.actual_result)).contains((self.expected_result))\n            elif self.comparator in [\"notContains\", \"does no contains\", \"不包含\"]:  # 字符串不包含该字符\n                assFailMsg = '实际值({}) 不包含 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).does_not_contain(*LMAssert.str2list(self.expected_result))\n            elif self.comparator in [\"containsOnly\", \"仅包含\"]:  # 字符串仅包含该字符\n                assFailMsg = '实际值({}) 仅包含 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).contains_only(*LMAssert.str2list(self.expected_result))\n            elif self.comparator in [\"isNone\", \"none/null\"]:  # 为none或null\n                assFailMsg = '实际值({}) 为none或null，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2none(self.actual_result)).is_none()\n            elif self.comparator in [\"notEmpty\", \"is not empty\", \"不为空\"]:  # 不为空\n                assFailMsg = '实际值({}) 不为空，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_not_empty()\n            elif self.comparator in [\"empty\", \"is empty\", \"为空\"]:  # 为空\n                assFailMsg = '实际值({}) 为空，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_empty()\n            elif self.comparator in [\"isTrue\", \"true\"]:  # 是true\n                assFailMsg = '实际值({}) 是true，条件为否：'.format(self.actual_result, self.expected_result)\n                res = False if LMAssert.str2bool(self.actual_result) is None else LMAssert.str2bool(self.actual_result)\n                assertpy.assert_that(res).is_true()\n            elif self.comparator in [\"isFalse\", \"false\"]:  # 是false\n                assFailMsg = '实际值({}) 是false，条件为否：'.format(self.actual_result, self.expected_result)\n                res = True if LMAssert.str2bool(self.actual_result) is None else LMAssert.str2bool(self.actual_result)\n                assertpy.assert_that(res).is_false()\n            elif self.comparator in [\"isStrType\", \"字符串\"]:  # 是str的类型\n                assFailMsg = '实际值({}) 是字符串，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_type_of(str)\n            elif self.comparator in [\"isIntType\", \"整数\"]:  # 是int的类型\n                assFailMsg = '实际值({}) 是整数，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_type_of(int)\n            elif self.comparator in [\"isFloatType\", \"浮点数\"]:  # 是浮点的类型\n                assFailMsg = '实际值({}) 是浮点数，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_type_of(float)\n            elif self.comparator in [\"isInt\", \"is a number\", \"仅含数字\"]:  # 字符串中仅含有数字\n                assFailMsg = '实际值({}) 仅含数字，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_digit()\n            elif self.comparator in [\"isLetter\", \"仅含字母\"]:  # 字符串中仅含有字母\n                assFailMsg = '实际值({}) 仅含字母，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_alpha()\n            elif self.comparator in [\"isLower\", \"小写\"]:  # 是小写的\n                assFailMsg = '实际值({}) 是小写的，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_lower()\n            elif self.comparator in [\"isUpper\", \"大写\"]:  # 是大写的\n                assFailMsg = '实际值({}) 是大写的，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_upper()\n            elif self.comparator in [\"startWith\", \"开头是\"]:  # 字符串以该字符开始\n                assFailMsg = '实际值({}) 开头是 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).starts_with(self.expected_result)\n            elif self.comparator in [\"endWith\", \"结尾是\"]:  # 字符串以该字符结束\n                assFailMsg = '实际值({}) 结尾是 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).ends_with(self.expected_result)\n            elif self.comparator in [\"isIn\", \"has item\", \"包含对象\", \"被包含\"]:  # 在这几个字符串中\n                assFailMsg = '实际值({}) 被包含在 预期值({}) 列表中，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_in(*LMAssert.str2list(self.expected_result))\n            elif self.comparator in [\"isNotIn\", \"不被包含\"]:  # 不在这几个字符串中\n                assFailMsg = '实际值({}) 不被包含在 预期值({}) 列表中，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_not_in(*LMAssert.str2list(self.expected_result))\n            elif self.comparator in [\"isNotZero\", \"非0\"]:  # 不是0\n                assFailMsg = '实际值({}) 不是0，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_not_zero()\n            elif self.comparator in [\"isZero\", \"为0\"]:  # 是0\n                assFailMsg = '实际值({}) 是0，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_zero()\n            elif self.comparator in [\"isPositive\", \"正数\"]:  # 是正数\n                assFailMsg = '实际值({}) 是正数，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_positive()\n            elif self.comparator in [\"isNegative\", \"负数\"]:  # 是负数\n                assFailMsg = '实际值({}) 是负数，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(self.actual_result).is_negative()\n            elif self.comparator in [\"isGreaterThan\", \" 大于\"]:  # 大于\n                assFailMsg = '实际值({}) 大于 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_greater_than(LMAssert.str2num(self.expected_result))\n            elif self.comparator in [\"isGreaterThanOrEqualTo\", \"greater than or equal\", \">=\", \" 大于等于\"]:  # 大于等于\n                assFailMsg = '实际值({}) 大于等于 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_greater_than_or_equal_to(LMAssert.str2num(self.expected_result))\n            elif self.comparator in [\"isLessThan\", \" 小于\"]:  # 小于\n                assFailMsg = '实际值({}) 小于 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_less_than(LMAssert.str2num(self.expected_result))\n            elif self.comparator in [\"isLessThanOrEqualTo\", \"less than or equal\", \"<=\", \" 小于等于\"]:  # 小于等于\n                assFailMsg = '实际值({}) 小于等于 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_less_than_or_equal_to(LMAssert.str2num(self.expected_result))\n            elif self.comparator in [\"isBetween\", \" 在...之间\"]:  # 在...之间\n                assFailMsg = '实际值({}) 在 预期值({}) 之间，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_between(*LMAssert.str2list(self.expected_result))\n            elif self.comparator in [\"isCloseTo\", \" 接近于\"]:  # 接近于\n                assFailMsg = '实际值({}) 接近于 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.str2num(self.actual_result)).is_close_to(*LMAssert.str2list(self.expected_result))\n            elif self.comparator in [\"listLenEqual\",\"列表长度相等\"]:  # 列表长度相等\n                assFailMsg = '实际值({}) 列表长度相等 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.list_len(self.actual_result)).is_equal_to(LMAssert.str2num(self.expected_result))\n            elif self.comparator in [\"listLenGreaterThan\",\"列表长度大于\"]:  # 列表长度大于\n                assFailMsg = '实际值({}) 列表长度大于 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.list_len(self.actual_result)).is_greater_than(LMAssert.str2num(self.expected_result))\n            elif self.comparator in [\"listLenLessThan\",\"列表长度小于\"]:  # 列表长度小于\n                assFailMsg = '实际值({}) 列表长度小于 预期值({})，条件为否：'.format(self.actual_result, self.expected_result)\n                assertpy.assert_that(LMAssert.list_len(self.actual_result)).is_less_than_or_equal_to(LMAssert.str2num(self.expected_result))\n            else:\n                raise AssertionTypeNotExist('没有{}该断言类型'.format(self.comparator))\n            return True, 'success'\n        except AssertionError as e:\n            ex = str(e).replace(\"Expected <\", \"Expected (\").replace(\">, \", \"), \").replace(\" <\", \" (\").replace(\"> \", \") \")\n            return False, assFailMsg + ex\n\n    @staticmethod\n    def str2none(value):\n        if str(value).lower() == \"none\" or str(value).lower() == \"null\":\n            return None\n        else:\n            return value\n\n    @staticmethod\n    def str2bool(value):\n        if str(value).lower() == \"true\":\n            return True\n        elif str(value).lower() == \"false\":\n            return False\n        else:\n            return None\n\n    @staticmethod\n    def str2num(value):\n        if type(value) == int or type(value) == float:\n            return value\n        if value is None or len(value) == 0:\n            return None\n        elif re.fullmatch(r'-?[0-9]*\\.?[0-9]*', value) is not None:\n            if '.' in value:\n                return float(value)\n            else:\n                return int(value)\n        else:\n            return value\n\n    @staticmethod\n    def str2list(value):\n        if type(value) == list or type(value) == int or type(value) == float:\n            return value\n        if value is None or len(value) == 0:\n            return None\n        value_list = []\n        if value.startswith('[') and value.endswith(']'):\n            for item in value[1:-1].split(','):\n                item_strip = item.strip()\n                if re.fullmatch(r'-?[0-9]*\\.?[0-9]*', item_strip) is not None:\n                    if '.' in item_strip:\n                        value_list.append(float(item_strip))\n                    else:\n                        value_list.append(int(item_strip))\n                else:  # 字符串\n                    value_list.append(item_strip[1:-1])\n            return value_list\n        else:\n            return value\n\n    @staticmethod\n    def str2dict(value):\n        if type(value) == dict or type(value) == int or type(value) == float:\n            return value\n        if value is None or len(value) == 0:\n            return None\n        value_dict={}\n        if value.startswith('{') and value.endswith('}'):\n            for item in value[1:-1].split(','):\n                value_dict = ast.literal_eval(value)\n            return value_dict\n        else:\n            return value\n\n    @staticmethod\n    def to_str(value):\n        if type(value) == int or type(value) == float:\n            return value\n        if value is None or len(value) == 0:\n            return \"\"\n        if type(value) == str:\n            return value\n        else:\n            return str(value)\n\n    @staticmethod\n    def list_len(value):\n        value2list=LMAssert.str2list(value)\n        if type(value2list) != list:\n            raise AssertionTypeNotExist('传入实际值({}) 不是列表格式'.format(value))\n        else:\n            return len(value2list)\n\n\nclass AssertionTypeNotExist(Exception):\n    \"\"\"断言类型错误\"\"\"\n"
  },
  {
    "path": "core/template.py",
    "content": "from functools import reduce\nfrom hashlib import md5\nfrom jsonpath import jsonpath\nfrom jsonpath_ng.parser import JsonPathParser\nfrom tools.funclib import get_func_lib\nimport json\nimport re\nimport time\n\nfrom tools.utils.utils import extract_by_jsonpath, quotation_marks\n\n\nclass Template:\n\n    def __init__(self, test, context, functions, params, variable_start_string='{{', variable_end_string='}}', function_prefix='@', param_prefix='$'):\n        self.test = test\n        self.param_prefix = param_prefix\n        self.data = None\n        self.context = context  # 关联参数\n        self.params = params    # 公共参数\n        self.variable_start_string = variable_start_string\n        self.variable_end_string = variable_end_string\n        self.function_prefix = function_prefix\n        self.param_prefix = param_prefix\n        self.stack = list()\n        # 动态存储接口的请求信息 以便渲染\n        self.request_url = None\n        self.request_path = None\n        self.request_headers = None\n        self.request_query = None\n        self.request_body = None\n        self.func_lib = get_func_lib(test, functions, self.context, self.params)\n        self.bytes_map = dict()\n        self.parser = JsonPathParser()\n\n    def init(self, data):\n        self.data = json.dumps(data, ensure_ascii=False)\n        self.stack.clear()\n        self.bytes_map.clear()\n\n    def set_help_data(self, url, path: str, headers: dict, query: dict, body: dict):\n        self.request_url = url\n        self.request_path = path\n        self.request_headers = headers\n        self.request_query = query\n        self.request_body = body\n\n    def render(self):\n        start_stack = list()\n        start_length = len(self.variable_start_string)\n        end_length = len(self.variable_end_string)\n        top = 0\n        flag = False\n        for cur in range(len(self.data)):\n            self.stack.append(self.data[cur])\n            top += 1\n            if flag:\n                self.stack.pop()\n                top -= 1\n                flag = False\n                continue\n            if reduce(lambda x, y: x + y, self.stack[-start_length:]) == self.variable_start_string:\n                start_stack.append(top - start_length)\n            if reduce(lambda x, y: x + y, self.stack[-end_length:]) == self.variable_end_string:\n                if len(start_stack) == 0:\n                    continue\n                recent = start_stack.pop()\n                tmp = ''\n                for _ in range(top - recent):\n                    tmp += self.stack.pop()\n                    top -= 1\n                if self.stack[-1] == '\"' and self.data[cur + 1] == '\"':\n                    self.stack.pop()\n                    top -= 1\n                    flag = True\n                else:\n                    flag = False\n                tmp = tmp[::-1]\n                key = tmp[start_length:-end_length].strip()\n                key, json_path = self.split_key(key)\n                try:\n                    if key.startswith(self.function_prefix):\n                        name_args = self.split_func(key, self.function_prefix)\n                        value = self.func_lib(name_args[0], *name_args[1:])\n                    elif key in self.context: # 优先从关联参数中取\n                        if json_path is None:\n                            value = self.context.get(key)\n                        else:\n                            value = extract_by_jsonpath(self.context.get(key), json_path)\n                    elif key in self.params:\n                        if json_path is None:\n                            value = self.params.get(key)\n                        else:\n                            value = extract_by_jsonpath(self.params.get(key), json_path)\n                    elif key.startswith(self.param_prefix) and key[1:] in self.params:  # 兼容老版本\n                        if json_path is None:\n                            value = self.params.get(key[1:])\n                        else:\n                            value = extract_by_jsonpath(self.params.get(key[1:]), json_path)\n                    else:\n                        value = tmp\n                except:\n                    value = tmp\n                    print('不存在的公共参数、关联变量或内置函数: {}'.format(key), file=self.test.stdout_buffer)\n\n                if not flag and isinstance(value, str):\n                    if '\"' in value and value != tmp:\n                        value = json.dumps(value)[1:-1]\n                    final_value = value\n                elif isinstance(value, bytes):\n                    final_value = self._bytes_save(value, flag)\n                elif isinstance(value, list):\n                    final_value = list()\n                    for list_item in value:\n                        if isinstance(list_item, bytes):\n                            final_value.append(self._bytes_save(list_item, False))\n                        else:\n                            final_value.append(list_item)\n                    final_value = json.dumps(final_value)\n                else:\n                    if value == tmp and isinstance(value, str):\n                        final_value = '\"'+value+'\"'\n                    else:\n                        final_value = json.dumps(value)\n                for s in final_value:\n                    self.stack.append(s)\n                    top += 1\n        res = json.loads(reduce(lambda x, y: x + y, self.stack))\n\n        if len(self.bytes_map) > 0:\n            pattern = r'#\\{(bytes_\\w+_\\d+?)\\}'\n            if isinstance(res, str):\n                bytes_value = self._bytes_slove(res, pattern)\n                if bytes_value is not None:\n                    res = bytes_value\n            elif isinstance(res, dict) or isinstance(res, list):\n                for i, j in zip(jsonpath(res, '$..'), jsonpath(res, '$..', result_type='PATH')):\n                    if isinstance(i, str):\n                        bytes_value = self._bytes_slove(i, pattern)\n                        if bytes_value is not None:\n                            expression = self.parser.parse(j)\n                            expression.update(res, bytes_value)\n        return res\n\n    def _bytes_save(self, value, flag):\n        bytes_map_key = 'bytes_{}_{}'.format(md5(value).hexdigest(), int(time.time() * 1000000000))\n        self.bytes_map[bytes_map_key] = value\n        change_value = '#{%s}' % bytes_map_key\n        if flag:\n            final_value = json.dumps(change_value)\n        else:\n            final_value = change_value\n        return final_value\n\n    def _bytes_slove(self, s, pattern):\n        search_result = re.search(pattern, s)\n        if search_result is not None:\n            expr = search_result.group(1)\n            return self.bytes_map[expr]\n\n    def replace_param(self, param):\n        param = param.strip()\n        search_result = re.search(r'#\\{(.*?)\\}', param)\n        if search_result is not None:\n            expr = search_result.group(1).strip()\n            if expr.lower() == '_request_url':\n                return self.request_url\n            elif expr.lower() == '_request_path':\n                return self.request_path\n            elif expr.lower() == '_request_header':\n                return self.request_headers\n            elif expr.lower() == '_request_body':\n                return self.request_body\n            elif expr.lower() == '_request_query':\n                return self.request_query\n            elif expr.startswith('bytes_'):\n                return self.bytes_map[expr]\n            else:\n                # 支持从请求头和查询参数中取单个数据\n                if expr.lower().startswith(\"_request_header.\"):\n                    data = self.request_headers\n                    expr = '$.' + expr[16:]\n                elif expr.lower().startswith(\"_request_query.\"):\n                    data = self.request_query\n                    expr = '$.' + expr[15:]\n                else:\n                    data = self.request_body\n                    if expr.lower().startswith(\"_request_body.\"):\n                        expr = '$.' + expr[14:]\n                    elif not expr.startswith('$'):\n                        expr = '$.' + expr\n                try:\n                    return extract_by_jsonpath(data, expr)\n                except:\n                    return param\n        else:\n            return param\n\n    def split_key(self, key: str):\n        if key.startswith(self.function_prefix):\n            return key, None\n        key_list = key.split(\".\")\n        key = key_list[0]\n        json_path = None\n        if len(key_list) > 1:\n            json_path = reduce(lambda x, y: x + '.' + y, key_list[1:])\n        if key.endswith(']') and '[' in key:\n            keys = key.split(\"[\")\n            key = keys[0]\n            if json_path is None:\n                json_path = keys[-1][:-1]\n            else:\n                json_path = keys[-1][:-1] + \".\" + json_path\n        if json_path is not None:\n            json_path = \"$.\" + json_path\n        return key, json_path\n\n    def split_func(self, statement: str, flag: 'str' = '@'):\n        pattern = flag + r'([_a-zA-Z][_a-zA-Z0-9]*)(\\(.*?\\))?'\n        m = re.match(pattern, statement)\n        result = list()\n        if m is not None:\n            name, _ = m.groups()\n            args = statement.replace(flag+name, \"\")\n            result.append(name)\n            if args is not None and args != '()':\n                argList = [str(_) for _ in map(self.replace_param, args[1:-1].split(','))]\n                argList_length = len(argList)\n                if not (argList_length == 1 and len(argList[0]) == 0):\n                    if name not in self.func_lib.func_param:\n                        for i in range(argList_length):\n                            result.append(argList[i])\n                    else:\n                        type_list = self.func_lib.func_param[name]\n                        j = 0\n                        for i in range(len(type_list)):\n                            if j >= argList_length:\n                                break\n                            if type_list[i] is str:\n                                result.append(quotation_marks(argList[j]))\n                                j += 1\n                            elif type_list[i] is int:\n                                result.append(int(argList[j]))\n                                j += 1\n                            elif type_list[i] is float:\n                                result.append(float(argList[j]))\n                                j += 1\n                            elif type_list[i] is bool:\n                                result.append(False if argList[j].lower() == 'false' else True)\n                                j += 1\n                            elif type_list[i] is dict:\n                                j, r = self.concat(j, argList, '}')\n                                result.append(r)\n                            elif type_list[i] is list:\n                                j, r = self.concat(j, argList, ']')\n                                result.append(r)\n                            elif type_list[i] is bytes:\n                                result.append(argList[j])\n                                j += 1\n                            elif type_list[i] is None:\n                                result.append(argList[j])\n                                j += 1\n                            else:\n                                raise SplitFunctionError('函数{}第{}个参数类型错误: {}'.format(name, i + 1, type_list[i]))\n            return result\n        else:\n            raise SplitFunctionError('函数错误: {}'.format(statement))\n\n    @staticmethod\n    def concat(start: int, arg_list: list, terminal_char: str):\n        end = start\n        length = len(arg_list)\n        for i in range(start, length):\n            if terminal_char in arg_list[i]:\n                end = i\n                s = reduce(lambda x, y: x + ',' + y, arg_list[start:end + 1])\n                try:\n                    return end + 1, eval(quotation_marks(s))\n                except:\n                    try:\n                        s = '\"'+s+'\"'\n                        return end + 1, eval(json.loads(s))\n                    except:\n                        continue\n        else:\n            s = reduce(lambda x, y: x + ',' + y, arg_list[start:end + 1])\n            return end + 1, s\n\n\nclass SplitFunctionError(Exception):\n    \"\"\"函数处理错误\"\"\"\n"
  },
  {
    "path": "core/web/collector.py",
    "content": "from selenium.webdriver.common.by import By\n\n\nlocator = {\n    \"ID\": By.ID,\n    \"XPATH\": \"xpath\",\n    \"LINK\": \"link text\",\n    \"PARTIAL\": \"partial link text\",\n    \"NAME\": \"name\",\n    \"TAG\": \"tag name\",\n    \"CLASS\": \"class name\",\n    \"CSS\": \"css selector\"\n}\n\n\nclass WebOperationCollector:\n\n    def __init__(self):\n        self.id = None\n        self.opt_type = None\n        self.opt_name = None\n        self.opt_trans = None\n        self.opt_element = None\n        self.opt_data = None\n        self.opt_code = None\n\n    @staticmethod\n    def __parse(ui_data: dict, name):\n        if name not in ui_data:\n            return None\n        return ui_data.get(name)\n\n    def collect_id(self, ui_data):\n        self.id = WebOperationCollector.__parse(ui_data, \"operationId\")\n\n    def collect_opt_type(self, ui_data):\n        self.opt_type = WebOperationCollector.__parse(ui_data, \"operationType\")\n\n    def collect_opt_name(self, ui_data):\n        self.opt_name = WebOperationCollector.__parse(ui_data, \"operationName\")\n\n    def collect_opt_trans(self, ui_data):\n        self.opt_trans = WebOperationCollector.__parse(ui_data, \"operationTrans\")\n\n    def collect_opt_code(self, ui_data):\n        self.opt_code = WebOperationCollector.__parse(ui_data, \"operationCode\")\n\n    def collect_opt_element(self, ui_data):\n        opt_element = WebOperationCollector.__parse(ui_data, \"operationElement\")\n        if opt_element is None or len(opt_element) == 0:\n            self.opt_element = None\n        else:\n            elements = {}\n            for name, element in opt_element.items():\n                elements[name] = (locator[element[\"by\"]], element[\"expression\"])\n            self.opt_element = elements\n\n    def collect_opt_data(self, ui_data):\n        opt_data = WebOperationCollector.__parse(ui_data, \"operationData\")\n        if opt_data is None or len(opt_data) == 0:\n            self.opt_data = None\n        else:\n            self.opt_data = opt_data\n\n    def collect(self, ui_data):\n        self.collect_id(ui_data)\n        self.collect_opt_type(ui_data)\n        self.collect_opt_name(ui_data)\n        self.collect_opt_trans(ui_data)\n        self.collect_opt_element(ui_data)\n        self.collect_opt_data(ui_data)\n        self.collect_opt_code(ui_data)\n"
  },
  {
    "path": "core/web/driver/__init__.py",
    "content": "from selenium.common.exceptions import NoSuchElementException\n\n\nclass Operation(object):\n    def __init__(self, test, driver):\n        self.driver = driver\n        self.test = test\n        self.print = print\n\n    def find_element(self, ele):\n        \"\"\"查找单个元素\"\"\"\n        try:\n            element = self.driver.find_element(*tuple(ele))\n            self.test.debugLog(\"成功定位元素 'By: %s Expression: %s'\" % ele)\n            return element\n        except Exception as e:\n            self.test.errorLog(\"无法定位元素 'By: %s Expression: %s'\" % ele)\n            raise e\n\n    def find_elements(self, ele):\n        \"\"\"查找批量元素\"\"\"\n        try:\n            elements = self.driver.find_elements(*tuple(ele))\n            if len(elements) > 0:\n                self.test.debugLog(\"成功定位元素 'By: %s Expression: %s'\" % ele)\n                return elements\n            else:\n                self.test.errorLog(\"无法定位元素 'By: %s Expression: %s'\" % ele)\n                raise NoSuchElementException(\"Failed to find elements 'By: %s Expression: %s'\" % ele)\n        except Exception as e:\n            self.test.errorLog(\"无法定位元素 'By: %s Expression: %s'\" % ele)\n            raise e\n\n"
  },
  {
    "path": "core/web/driver/assertionOpt.py",
    "content": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\n\nfrom core.assertion import LMAssert\nfrom core.web.driver import Operation\n\n\nclass Assertion(Operation):\n    \"\"\"断言类操作\"\"\"\n\n    def assert_page_title(self, assertion, expect):\n        \"\"\"断言页面标题\"\"\"\n        try:\n            actual = self.driver.title\n            self.test.debugLog(\"成功获取title:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取title\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_page_url(self, assertion, expect):\n        \"\"\"断言页面url\"\"\"\n        try:\n            actual = self.driver.current_url\n            self.test.debugLog(\"成功获取url:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取url\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_page_source(self, assertion, expect):\n        \"\"\"断言页面源码\"\"\"\n        try:\n            actual = self.driver.page_source\n            self.test.debugLog(\"成功获取page source: 源码过长不予展示\")\n        except Exception as e:\n            self.test.errorLog(\"无法获取page source\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_text(self, element, assertion, expect):\n        \"\"\"断言元素文本\"\"\"\n        try:\n            actual = self.find_element(element).text\n            self.test.debugLog(\"成功获取元素text:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素text\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_tag(self, element, assertion, expect):\n        \"\"\"断言元素tag\"\"\"\n        try:\n            actual = self.find_element(element).tag_name\n            self.test.debugLog(\"成功获取元素tag name:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素tag name\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_size(self, element, assertion, expect):\n        \"\"\"断言元素尺寸\"\"\"\n        try:\n            actual = self.find_element(element).size\n            self.test.debugLog(\"成功获取元素size:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素size\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_height(self, element, assertion, expect):\n        \"\"\"断言元素高度\"\"\"\n        try:\n            actual = self.find_element(element).size.get(\"height\")\n            self.test.debugLog(\"成功获取元素height:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素height\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_width(self, element, assertion, expect):\n        \"\"\"断言元素宽度\"\"\"\n        try:\n            actual = self.find_element(element).size.get(\"width\")\n            self.test.debugLog(\"成功获取元素width:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素width\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_location(self, element, assertion, expect):\n        \"\"\"断言元素位置\"\"\"\n        try:\n            actual = self.find_element(element).location\n            self.test.debugLog(\"成功获取元素location:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_x(self, element, assertion, expect):\n        \"\"\"断言元素X坐标\"\"\"\n        try:\n            actual = self.find_element(element).location.get(\"x\")\n            self.test.debugLog(\"成功获取元素location x:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location x\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_y(self, element, assertion, expect):\n        \"\"\"断言元素Y坐标\"\"\"\n        try:\n            actual = self.find_element(element).location.get(\"y\")\n            self.test.debugLog(\"成功获取元素location y:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location y\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_attribute(self, element, name, assertion, expect):\n        \"\"\"断言元素属性\"\"\"\n        try:\n            actual = self.find_element(element).get_attribute(name)\n            self.test.debugLog(\"成功获取元素attribute:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素attribute\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_selected(self, element, assertion, expect):\n        \"\"\"断言元素是否选中\"\"\"\n        try:\n            actual = self.find_element(element).is_selected()\n            self.test.debugLog(\"成功获取元素selected:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素selected\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_enabled(self, element, assertion, expect):\n        \"\"\"断言元素是否启用\"\"\"\n        try:\n            actual = self.find_element(element).is_enabled()\n            self.test.debugLog(\"成功获取元素enabled:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素enabled\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_displayed(self, element, assertion, expect):\n        \"\"\"断言元素是否显示\"\"\"\n        try:\n            actual = self.find_element(element).is_displayed()\n            self.test.debugLog(\"成功获取元素displayed:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素displayed\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_css(self, element, name, assertion, expect):\n        \"\"\"断言元素css样式\"\"\"\n        try:\n            actual = self.find_element(element).value_of_css_property(name)\n            self.test.debugLog(\"成功获取元素css %s:%s\" % (name, str(actual)))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素css %s\" % name)\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_ele_existed(self, element, assertion, expect):\n        \"\"\"断言元素是否存在\"\"\"\n        try:\n            try:\n                self.find_elements(element)\n                actual = True\n            except NoSuchElementException:\n                actual = False\n            self.test.debugLog(\"成功获取元素existed:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素existed\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_window_position(self, assertion, expect):\n        \"\"\"断言窗口位置\"\"\"\n        try:\n            actual = self.driver.get_window_position()\n            self.test.debugLog(\"成功获取窗口position:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_window_x(self, assertion, expect):\n        \"\"\"断言窗口X坐标\"\"\"\n        try:\n            actual = self.driver.get_window_position().get(\"x\")\n            self.test.debugLog(\"成功获取窗口position x:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position x\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_window_y(self, assertion, expect):\n        \"\"\"断言窗口Y坐标\"\"\"\n        try:\n            actual = self.driver.get_window_position().get(\"y\")\n            self.test.debugLog(\"成功获取窗口position y:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position y\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_window_size(self, assertion, expect):\n        \"\"\"断言窗口大小\"\"\"\n        try:\n            actual = self.driver.get_window_size()\n            self.test.debugLog(\"成功获取窗口size:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口size\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_window_width(self, assertion, expect):\n        \"\"\"断言窗口宽度\"\"\"\n        try:\n            actual = self.driver.get_window_size().get(\"width\")\n            self.test.debugLog(\"成功获取窗口width:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口width\")\n            raise e        \n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_window_height(self, assertion, expect):\n        \"\"\"断言窗口高度\"\"\"\n        try:\n            actual = self.driver.get_window_size().get(\"height\")\n            self.test.debugLog(\"成功获取窗口height:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口height\")\n            raise e        \n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_cookies(self, assertion, expect):\n        \"\"\"断言cookies\"\"\"\n        try:\n            actual = self.driver.get_cookies()\n            self.test.debugLog(\"成功获取cookies:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取cookies\")\n            raise e        \n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def assert_cookie(self, name, assertion, expect):\n        \"\"\"断言cookie\"\"\"\n        try:\n            actual = self.driver.get_cookie(name)\n            self.test.debugLog(\"成功获取cookie %s:%s\" % (name, str(actual)))\n        except Exception as e:\n            self.test.errorLog(\"无法获取cookie %s\" % name)\n            raise e        \n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"driver\"] = self.driver\n        names[\"test\"] = self.test\n        try:\n            \"\"\"断言操作需要返回被断言的值 以sys_return(value)返回\"\"\"\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_return(res):\n                names[\"_exec_result\"] = res\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n        else:\n            result, msg = LMAssert(kwargs[\"data\"][\"assertion\"], names[\"_exec_result\"], kwargs[\"data\"][\"expect\"]).compare()\n            return result, msg\n\n"
  },
  {
    "path": "core/web/driver/browserOpt.py",
    "content": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\n\nfrom core.web.driver import Operation\nfrom datetime import datetime\nfrom time import sleep\n\nfrom tools.utils.utils import url_join\n\n\nclass Browser(Operation):\n    \"\"\"浏览器类操作\"\"\"\n\n    def max_window(self):\n        \"\"\"最大化窗口\"\"\"\n        try:\n            self.driver.maximize_window()\n            self.test.debugLog(\"成功执行maximize window\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行maximize window\")\n            raise e\n\n    def min_window(self):\n        \"\"\"最小化窗口\"\"\"\n        try:\n            self.driver.minimize_window()\n            self.test.debugLog(\"成功执行minimize window\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行minimize window\")\n            raise e\n\n    def full_window(self):\n        \"\"\"全屏窗口\"\"\"\n        try:\n            self.driver.fullscreen_window()\n            self.test.debugLog(\"成功执行full screen window\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行full screen window\")\n            raise e\n\n    def set_position_window(self, x, y):\n        \"\"\"设置窗口位置\"\"\"\n        \"\"\"0,0是左上角\"\"\"\n        try:\n            self.driver.set_window_position(x, y)\n            self.test.debugLog(\"成功执行set window position\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行set window position\")\n            raise e\n\n    def set_size_window(self, width, height):\n        \"\"\"设置窗口大小\"\"\"\n        try:\n            self.driver.set_window_size(width, height)\n            self.test.debugLog(\"成功执行set window size\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行set window size\")\n            raise e\n\n    def switch_to_window(self, window):\n        \"\"\"切换窗口\"\"\"\n        try:\n            self.driver.switch_to.window(window)\n            self.test.debugLog(\"成功执行switch window\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行switch window\")\n            raise e\n\n    def close_window(self):\n        \"\"\"关闭窗口\"\"\"\n        try:\n            self.driver.close()\n            self.test.debugLog(\"成功执行close window\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行close window\")\n            raise e\n\n    def save_screenshot(self, name):\n        \"\"\"屏幕截图\"\"\"\n        try:\n            screenshot = self.driver.get_screenshot_as_png()\n            self.test.saveScreenShot(name, screenshot)\n            self.test.debugLog(\"成功执行screen shot\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行screen shot\")\n            raise e\n\n    def click_to_new_window(self, element):\n        \"\"\"单击跳转新窗口\"\"\"\n        try:\n            current = self.driver.window_handles\n            # 点击打开新窗口\n            self.find_element(element).click()\n            # 等待新窗口出现\n            current_time = datetime.now()\n            while (datetime.now()-current_time).seconds < 60:\n                if len(self.driver.window_handles) > len(current):\n                    for window_handle in self.driver.window_handles:\n                        if window_handle not in current:\n                            self.driver.switch_to.window(window_handle)\n                            self.test.debugLog(\"成功执行click and switch to new window\")\n                            return\n                else:\n                    sleep(2)\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行click and switch to new window\")\n            raise e\n\n    def back_and_close_window(self, window):\n        \"\"\"返回并关闭当前窗口\"\"\"\n        try:\n            self.driver.close()\n            self.driver.switch_to.window(window)\n            self.test.debugLog(\"成功执行back and close window\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行back and close window\")\n            raise e\n\n    def open_url(self, domain, path):\n        \"\"\"打开网页\"\"\"\n        try:\n            url = url_join(domain, path)\n            self.driver.get(url)\n            self.driver.implicitly_wait(2)\n            self.test.debugLog(\"成功打开 '%s'\" % url_join(domain, path))\n        except Exception as e:\n            self.test.errorLog(\"无法打开 '%s'\" % url_join(domain, path))\n            raise e\n\n    def refresh(self):\n        \"\"\"刷新页面\"\"\"\n        try:\n            self.driver.refresh()\n            self.test.debugLog(\"成功执行refresh\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行refresh\")\n            raise e\n\n    def back(self):\n        \"\"\"页面后退\"\"\"\n        try:\n            self.driver.back()\n            self.test.debugLog(\"成功执行back\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行back\")\n            raise e\n\n    def forward(self):\n        \"\"\"页面前进\"\"\"\n        try:\n            self.driver.forward()\n            self.test.debugLog(\"成功执行forward\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行forward\")\n            raise e\n\n    def add_cookie(self, name, value):\n        \"\"\"添加cookie\"\"\"\n        try:\n            self.driver.add_cookie({'name': name, 'value': value})\n            self.test.debugLog(\"成功执行add cookie: %s:%s\" % (name, value))\n        except Exception as e:\n            self.test.errorLog(\"无法执行add cookie: %s:%s\" % (name, value))\n            raise e\n\n    def delete_cookie(self, name):\n        \"\"\"删除cookie\"\"\"\n        try:\n            self.driver.delete_cookie(name)\n            self.test.debugLog(\"成功执行delete cookie:%s\" % name)\n        except Exception as e:\n            self.test.errorLog(\"无法执行delete cookie:%s\" % name)\n            raise e\n\n    def delete_cookies(self):\n        \"\"\"删除cookies\"\"\"\n        try:\n            self.driver.delete_all_cookies()\n            self.test.debugLog(\"成功执行delete cookies\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行delete cookies\")\n            raise e\n\n    def execute_script(self, script, arg:tuple):\n        \"\"\"执行脚本\"\"\"\n        try:\n            self.driver.execute_script(script, *arg)\n            self.test.debugLog(\"成功执行execute script:%s\" % script)\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行execute script:%s\" % script)\n            raise e\n\n    def execute_async_script(self, script, arg:tuple):\n        \"\"\"执行异步脚本\"\"\"\n        try:\n            self.driver.execute_async_script(script, *arg)\n            self.test.debugLog(\"成功执行execute async script:%s\" % script)\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行execute async script:%s\" % script)\n            raise e\n\n    def sleep(self, second):\n        \"\"\"强制等待\"\"\"\n        try:\n            sleep(second)\n            self.test.debugLog(\"成功执行sleep %ds\" % second)\n        except Exception as e:\n            self.test.errorLog(\"无法执行sleep %ds\" % second)\n            raise e\n\n    def implicitly_wait(self, second):\n        \"\"\"隐式等待\"\"\"\n        try:\n            self.driver.implicitly_wait(second)\n            self.test.debugLog(\"成功执行implicitly wait %ds\" % second)\n        except Exception as e:\n            self.test.errorLog(\"无法执行implicitly wait %ds\" % second)\n            raise e\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"driver\"] = self.driver\n        names[\"test\"] = self.test\n        try:\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n"
  },
  {
    "path": "core/web/driver/conditionOpt.py",
    "content": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\nfrom core.assertion import LMAssert\nfrom core.web.driver import Operation\n\n\nclass Condition(Operation):\n    \"\"\"条件类操作\"\"\"\n\n    def condition_page_title(self, assertion, expect):\n        \"\"\"判断页面标题\"\"\"\n        try:\n            actual = self.driver.title\n            self.test.debugLog(\"成功获取title:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取title\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_page_url(self, assertion, expect):\n        \"\"\"判断页面url\"\"\"\n        try:\n            actual = self.driver.current_url\n            self.test.debugLog(\"成功获取url:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取url\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_page_source(self, assertion, expect):\n        \"\"\"判断页面源码\"\"\"\n        try:\n            actual = self.driver.page_source\n            self.test.debugLog(\"成功获取page source: : 源码过长不予展示\")\n        except Exception as e:\n            self.test.errorLog(\"无法获取page source\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_text(self, element, assertion, expect):\n        \"\"\"判断元素文本\"\"\"\n        try:\n            actual = self.find_element(element).text\n            self.test.debugLog(\"成功获取元素text:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素text\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_tag(self, element, assertion, expect):\n        \"\"\"判断元素tag\"\"\"\n        try:\n            actual = self.find_element(element).tag_name\n            self.test.debugLog(\"成功获取元素tag name:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素tag name\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_size(self, element, assertion, expect):\n        \"\"\"判断元素尺寸\"\"\"\n        try:\n            actual = self.find_element(element).size\n            self.test.debugLog(\"成功获取元素size:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素size\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_height(self, element, assertion, expect):\n        \"\"\"判断元素高度\"\"\"\n        try:\n            actual = self.find_element(element).size.get(\"height\")\n            self.test.debugLog(\"成功获取元素height:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素height\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_width(self, element, assertion, expect):\n        \"\"\"判断元素宽度\"\"\"\n        try:\n            actual = self.find_element(element).size.get(\"width\")\n            self.test.debugLog(\"成功获取元素width:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素width\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_location(self, element, assertion, expect):\n        \"\"\"判断元素位置\"\"\"\n        try:\n            actual = self.find_element(element).location\n            self.test.debugLog(\"成功获取元素location:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_x(self, element, assertion, expect):\n        \"\"\"判断元素X坐标\"\"\"\n        try:\n            actual = self.find_element(element).location.get(\"x\")\n            self.test.debugLog(\"成功获取元素location x:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location x\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_y(self, element, assertion, expect):\n        \"\"\"判断元素Y坐标\"\"\"\n        try:\n            actual = self.find_element(element).location.get(\"y\")\n            self.test.debugLog(\"成功获取元素location y:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location y\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_attribute(self, element, name, assertion, expect):\n        \"\"\"判断元素属性\"\"\"\n        try:\n            actual = self.find_element(element).get_attribute(name)\n            self.test.debugLog(\"成功获取元素attribute:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素attribute\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_selected(self, element, assertion, expect):\n        \"\"\"判断元素是否选中\"\"\"\n        try:\n            actual = self.find_element(element).is_selected()\n            self.test.debugLog(\"成功获取元素selected:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素selected\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_enabled(self, element, assertion, expect):\n        \"\"\"判断元素是否启用\"\"\"\n        try:\n            actual = self.find_element(element).is_enabled()\n            self.test.debugLog(\"成功获取元素enabled:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素enabled\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_displayed(self, element, assertion, expect):\n        \"\"\"判断元素是否显示\"\"\"\n        try:\n            actual = self.find_element(element).is_displayed()\n            self.test.debugLog(\"成功获取元素displayed:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素displayed\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_css(self, element, name, assertion, expect):\n        \"\"\"判断元素css样式\"\"\"\n        try:\n            actual = self.find_element(element).value_of_css_property(name)\n            self.test.debugLog(\"成功获取元素css %s:%s\" % (name, str(actual)))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素css %s\" % name)\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_ele_existed(self, element, assertion, expect):\n        \"\"\"判断元素是否存在\"\"\"\n        try:\n            try:\n                self.find_elements(element)\n                actual = True\n            except NoSuchElementException:\n                actual = False\n            self.test.debugLog(\"成功获取元素existed:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素existed\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_window_position(self, assertion, expect):\n        \"\"\"判断窗口位置\"\"\"\n        try:\n            actual = self.driver.get_window_position()\n            self.test.debugLog(\"成功获取窗口position:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_window_x(self, assertion, expect):\n        \"\"\"判断窗口X坐标\"\"\"\n        try:\n            actual = self.driver.get_window_position().get(\"x\")\n            self.test.debugLog(\"成功获取窗口position x:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position x\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_window_y(self, assertion, expect):\n        \"\"\"判断窗口Y坐标\"\"\"\n        try:\n            actual = self.driver.get_window_position().get(\"y\")\n            self.test.debugLog(\"成功获取窗口position y:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position y\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_window_size(self, assertion, expect):\n        \"\"\"判断窗口大小\"\"\"\n        try:\n            actual = self.driver.get_window_size()\n            self.test.debugLog(\"成功获取窗口size:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口size\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_window_width(self, assertion, expect):\n        \"\"\"判断窗口宽度\"\"\"\n        try:\n            actual = self.driver.get_window_size().get(\"width\")\n            self.test.debugLog(\"成功获取窗口width:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口width\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_window_height(self, assertion, expect):\n        \"\"\"判断窗口高度\"\"\"\n        try:\n            actual = self.driver.get_window_size().get(\"height\")\n            self.test.debugLog(\"成功获取窗口height:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口height\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_cookies(self, assertion, expect):\n        \"\"\"判断cookies\"\"\"\n        try:\n            actual = self.driver.get_cookies()\n            self.test.debugLog(\"成功获取cookies:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取cookies\")\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n\n    def condition_cookie(self, name, assertion, expect):\n        \"\"\"判断cookie\"\"\"\n        try:\n            actual = self.driver.get_cookie(name)\n            self.test.debugLog(\"成功获取cookie %s:%s\" % (name, str(actual)))\n        except Exception as e:\n            self.test.errorLog(\"无法获取cookie %s\" % name)\n            raise e\n        else:\n            result, msg = LMAssert(assertion, actual, expect).compare()\n            return result, msg\n    \n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"driver\"] = self.driver\n        names[\"test\"] = self.test\n        try:\n            \"\"\"条件操作需要返回被断言的值 以sys_return(value)返回\"\"\"\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_return(res):\n                names[\"_exec_result\"] = res\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n        else:\n            result, msg = LMAssert(kwargs[\"data\"][\"assertion\"], names[\"_exec_result\"], kwargs[\"data\"][\"expect\"]).compare()\n            return result, msg\n\n"
  },
  {
    "path": "core/web/driver/pageOpt.py",
    "content": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\nfrom selenium.webdriver import ActionChains\nfrom selenium.webdriver.common.keys import Keys\nfrom selenium.webdriver.support import wait, expected_conditions\nfrom selenium.webdriver.support.wait import WebDriverWait\n\nfrom core.web.driver import Operation\n\n\nclass Page(Operation):\n    \"\"\"网页类操作\"\"\"\n\n    def switch_frame(self, frame):\n        \"\"\"切换框架\"\"\"\n        try:\n            frame_reference = self.find_element(frame)\n            self.driver.switch_to.frame(frame_reference)\n            self.test.debugLog(\"成功切换frame:%s\" % frame[1])\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法切换frame:%s\" % frame[1])\n            raise e\n\n    def switch_content(self):\n        \"\"\"返回默认框架\"\"\"\n        try:\n            self.driver.switch_to.default_content()\n            self.test.debugLog(\"成功切换default content\")\n        except Exception as e:\n            self.test.errorLog(\"无法切换default content\")\n            raise e\n\n    def switch_parent(self):\n        \"\"\"返回父框架\"\"\"\n        try:\n            self.driver.switch_to.parent_frame()\n            self.test.debugLog(\"成功切换parent content\")\n        except Exception as e:\n            self.test.errorLog(\"无法切换parent content\")\n            raise e\n\n    def alert_accept(self):\n        \"\"\"弹出框确认\"\"\"\n        try:\n            alert = wait.WebDriverWait(self.driver, timeout=30).until(expected_conditions.alert_is_present())\n            alert.accept()\n            self.test.debugLog(\"成功执行alert accept\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行alert accept\")\n            raise e\n\n    def alert_input(self, text):\n        \"\"\"弹出框输入\"\"\"\n        try:\n            alert = wait.WebDriverWait(self.driver, timeout=30).until(expected_conditions.alert_is_present())\n            alert.send_keys(text)\n            self.test.debugLog(\"成功执行alert input\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行alert input\")\n            raise e\n\n    def alert_cancel(self):\n        \"\"\"弹出框取消\"\"\"\n        try:\n            alert = wait.WebDriverWait(self.driver, timeout=30).until(expected_conditions.alert_is_present())\n            alert.dismiss()\n            self.test.debugLog(\"成功执行alert cancel\")\n        except Exception as e:\n            self.test.errorLog(\"无法执行alert cancel\")\n            raise e\n\n    def free_click(self):\n        \"\"\"鼠标单击\"\"\"\n        try:\n            ActionChains(self.driver).click().perform()\n            self.test.debugLog(\"成功执行free click\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行free click\")\n            raise e\n\n    def clear(self, element):\n        \"\"\"清空\"\"\"\n        try:\n            self.find_element(element).clear()\n            self.test.debugLog(\"成功执行clear\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行clear\")\n            raise e\n\n    def input_text(self, element, text):\n        \"\"\"输入\"\"\"\n        try:\n            self.find_element(element).send_keys(text)\n            self.test.debugLog(\"成功执行文本输入:'%s'\" % text)\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行文本输入:'%s'\" % text)\n            raise e\n\n    def click(self, element):\n        \"\"\"单击\"\"\"\n        try:\n            self.find_element(element).click()\n            self.test.debugLog(\"成功执行click\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行click\")\n            raise e\n\n    def submit(self, element):\n        \"\"\"提交\"\"\"\n        try:\n            self.find_element(element).submit()\n            self.test.debugLog(\"成功执行submit\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行submit\")\n            raise e\n\n    def click_and_hold(self, element):\n        \"\"\"单击保持\"\"\"\n        try:\n            ele = self.find_element(element)\n            ActionChains(self.driver).click_and_hold(ele).perform()\n            self.test.debugLog(\"成功执行click and hold\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行click and hold\")\n            raise e\n\n    def context_click(self, element):\n        \"\"\"右键点击\"\"\"\n        try:\n            ele = self.find_element(element)\n            ActionChains(self.driver).context_click(ele).perform()\n            self.test.debugLog(\"成功执行context click\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行context click\")\n            raise e\n\n    def double_click(self, element):\n        \"\"\"双击\"\"\"\n        try:\n            ele = self.find_element(element)\n            ActionChains(self.driver).double_click(ele).perform()\n            self.test.debugLog(\"成功执行double click\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行double click\")\n            raise e\n\n    def drag_and_drop(self, start_element, end_element):\n        \"\"\"拖拽\"\"\"\n        try:\n            ele = self.find_element(start_element)\n            tar_ele = self.find_element(end_element)\n            ActionChains(self.driver).drag_and_drop(ele, tar_ele).perform()\n            self.test.debugLog(\"成功执行drag and drop to element\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行drag and drop to element\")\n            raise e\n\n    def drag_and_drop_by_offset(self, element, x, y):\n        \"\"\"偏移拖拽\"\"\"\n        try:\n            ele = self.find_element(element)\n            ActionChains(self.driver).drag_and_drop_by_offset(ele, x, y).perform()\n            self.test.debugLog(\"成功执行drag and drop to (%s, %s)\" % (x,y))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行drag and drop to (%s, %s)\" % (x,y))\n            raise e\n\n    def key_down(self, element, value):\n        \"\"\"按下键位\"\"\"\n        try:\n            ele = self.find_element(element)\n            if hasattr(Keys, value.upper()):\n                keys = getattr(Keys, value)\n            else:\n                raise Exception(\"键位%s不存在\" % value)\n            ActionChains(self.driver).key_down(keys, ele).perform()\n            self.test.debugLog(\"成功执行key down %s\" % value)\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行key down %s\" % value)\n            raise e\n\n    def key_up(self, element, value):\n        \"\"\"释放键位\"\"\"\n        try:\n            ele = self.find_element(element)\n            if hasattr(Keys, value.upper()):\n                keys = getattr(Keys, value)\n            else:\n                raise Exception(\"键位%s不存在\" % value)\n            ActionChains(self.driver).key_up(keys, ele).perform()\n            self.test.debugLog(\"成功执行key up %s\" % value)\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行key up %s\" % value)\n            raise e\n\n    def move_by_offset(self, x, y):\n        \"\"\"鼠标移动到坐标\"\"\"\n        try:\n            ActionChains(self.driver).move_by_offset(x, y).perform()\n            self.test.debugLog(\"成功执行move mouse to (%s, %s)\" % (x,y))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行move mouse to (%s, %s)\" % (x,y))\n            raise e\n\n    def move_to_element(self, element):\n        \"\"\"鼠标移动到元素\"\"\"\n        try:\n            ele = self.find_element(element)\n            ActionChains(self.driver).move_to_element(ele).perform()\n            self.test.debugLog(\"成功执行move mouse to element\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行move mouse to element\")\n            raise e\n\n    def move_to_element_with_offset(self, element, x, y):\n        \"\"\"鼠标移动到元素坐标\"\"\"\n        try:\n            ele = self.find_element(element)\n            ActionChains(self.driver).move_to_element_with_offset(ele, x, y).perform()\n            self.test.debugLog(\"成功执行move mouse to element with (%s, %s)\" % (x,y))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行move mouse to element with (%s, %s)\" % (x,y))\n            raise e\n\n    def release(self, element):\n        \"\"\"释放点击保持状态\"\"\"\n        try:\n            ele = self.find_element(element)\n            ActionChains(self.driver).release(ele).perform()\n            self.test.debugLog(\"成功执行release mouse\")\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行release mouse\")\n            raise e\n\n    def wait_element_appear(self, element, second):\n        \"\"\"等待元素出现\"\"\"\n        try:\n            WebDriverWait(self.driver, second, 0.2).until(expected_conditions.presence_of_element_located(element))\n            self.test.debugLog(\"成功执行wait %ds until element appear\" % second)\n        except Exception as e:\n            self.test.errorLog(\"无法执行wait %ds until element appear\" % second)\n            raise e\n\n    def wait_element_disappear(self, element, second):\n        \"\"\"等待元素消失\"\"\"\n        try:\n            WebDriverWait(self.driver, second, 0.2).until_not(expected_conditions.presence_of_element_located(element))\n            self.test.debugLog(\"成功执行wait %ds until element disappear\" % second)\n        except Exception as e:\n            self.test.errorLog(\"无法执行wait %ds until element disappear\" % second)\n            raise e\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"driver\"] = self.driver\n        names[\"test\"] = self.test\n        try:\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n"
  },
  {
    "path": "core/web/driver/relationOpt.py",
    "content": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\n\nfrom core.web.driver import Operation\n\n\nclass Relation(Operation):\n    \"\"\"关联类操作\"\"\"\n    def get_page_title(self, save_name):\n        \"\"\"获取页面标题\"\"\"\n        try:\n            actual = self.driver.title\n            self.test.debugLog(\"成功获取title:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取title\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_page_url(self, save_name):\n        \"\"\"获取页面url\"\"\"\n        try:\n            actual = self.driver.current_url\n            self.test.debugLog(\"成功获取url:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取url\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_text(self, element, save_name):\n        \"\"\"获取元素文本\"\"\"\n        try:\n            actual = self.find_element(element).text\n            self.test.debugLog(\"成功获取元素text:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素text\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_tag(self, element, save_name):\n        \"\"\"获取元素tag\"\"\"\n        try:\n            actual = self.find_element(element).tag_name\n            self.test.debugLog(\"成功获取元素tag name:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素tag name\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_size(self, element, save_name):\n        \"\"\"获取元素尺寸\"\"\"\n        try:\n            actual = self.find_element(element).size\n            self.test.debugLog(\"成功获取元素size:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素size\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_height(self, element, save_name):\n        \"\"\"获取元素高度\"\"\"\n        try:\n            actual = self.find_element(element).size.get(\"height\")\n            self.test.debugLog(\"成功获取元素height:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素height\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_width(self, element, save_name):\n        \"\"\"获取元素宽度\"\"\"\n        try:\n            actual = self.find_element(element).size.get(\"width\")\n            self.test.debugLog(\"成功获取元素width:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素width\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_location(self, element, save_name):\n        \"\"\"获取元素位置\"\"\"\n        try:\n            actual = self.find_element(element).location\n            self.test.debugLog(\"成功获取元素location:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_x(self, element, save_name):\n        \"\"\"获取元素X坐标\"\"\"\n        try:\n            actual = self.find_element(element).location.get(\"x\")\n            self.test.debugLog(\"成功获取元素location x:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location x\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_y(self, element, save_name):\n        \"\"\"获取元素Y坐标\"\"\"\n        try:\n            actual = self.find_element(element).location.get(\"y\")\n            self.test.debugLog(\"成功获取元素location y:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素location y\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_attribute(self, element, name, save_name):\n        \"\"\"获取元素属性\"\"\"\n        try:\n            actual = self.find_element(element).get_attribute(name)\n            self.test.debugLog(\"成功获取元素attribute:%s\" % str(actual))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素attribute\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_ele_css(self, element, name, save_name):\n        \"\"\"获取元素css样式\"\"\"\n        try:\n            actual = self.find_element(element).value_of_css_property(name)\n            self.test.debugLog(\"成功获取元素css %s:%s\" % (name, str(actual)))\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法获取元素css %s\" % name)\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_window_position(self, save_name):\n        \"\"\"获取窗口位置\"\"\"\n        try:\n            actual = self.driver.get_window_position()\n            self.test.debugLog(\"成功获取窗口position:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_window_x(self, save_name):\n        \"\"\"获取窗口X坐标\"\"\"\n        try:\n            actual = self.driver.get_window_position().get(\"x\")\n            self.test.debugLog(\"成功获取窗口position x:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position x\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_window_y(self, save_name):\n        \"\"\"获取窗口Y坐标\"\"\"\n        try:\n            actual = self.driver.get_window_position().get(\"y\")\n            self.test.debugLog(\"成功获取窗口position y:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口position y\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_window_size(self, save_name):\n        \"\"\"获取窗口大小\"\"\"\n        try:\n            actual = self.driver.get_window_size()\n            self.test.debugLog(\"成功获取窗口size:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口size\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_window_width(self, save_name):\n        \"\"\"获取窗口宽度\"\"\"\n        try:\n            actual = self.driver.get_window_size().get(\"width\")\n            self.test.debugLog(\"成功获取窗口width:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口width\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_window_height(self, save_name):\n        \"\"\"获取窗口高度\"\"\"\n        try:\n            actual = self.driver.get_window_size().get(\"height\")\n            self.test.debugLog(\"成功获取窗口height:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取窗口height\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_current_handle(self, save_name):\n        \"\"\"获取当前窗口句柄\"\"\"\n        try:\n            actual = self.driver.current_window_handle\n            self.test.debugLog(\"成功获取当前窗口handle:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取当前窗口handle\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_all_handle(self, save_name):\n        \"\"\"获取所有窗口句柄\"\"\"\n        try:\n            actual = self.driver.window_handles\n            self.test.debugLog(\"成功获取所有窗口handle:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取所有窗口handle\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_cookies(self, save_name):\n        \"\"\"获取cookies\"\"\"\n        try:\n            actual = self.driver.get_cookies()\n            self.test.debugLog(\"成功获取cookies:%s\" % str(actual))\n        except Exception as e:\n            self.test.errorLog(\"无法获取cookies\")\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def get_cookie(self, name, save_name):\n        \"\"\"获取cookie\"\"\"\n        try:\n            actual = self.driver.get_cookie(name)\n            self.test.debugLog(\"成功获取cookie %s:%s\" % (name, str(actual)))\n        except Exception as e:\n            self.test.errorLog(\"无法获取cookie:%s\" % name)\n            raise e\n        else:\n            self.test.context[save_name] = actual\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"driver\"] = self.driver\n        names[\"test\"] = self.test\n        try:\n            \"\"\"关联操作需要返回被断言的值 以sys_return(value)返回\"\"\"\n\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_return(res):\n                names[\"_exec_result\"] = res\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n        else:\n            self.test.context[kwargs[\"data\"][\"save_name\"]] = names[\"_exec_result\"]\n\n"
  },
  {
    "path": "core/web/driver/scenarioOpt.py",
    "content": "import sys\n\nfrom selenium.common.exceptions import NoSuchElementException\nfrom core.web.driver import Operation\n\n\nclass Scenario(Operation):\n    \"\"\"场景类类操作\"\"\"\n\n    def custom(self, **kwargs):\n        \"\"\"自定义\"\"\"\n        code = kwargs[\"code\"]\n        names = locals()\n        names[\"element\"] = kwargs[\"element\"]\n        names[\"data\"] = kwargs[\"data\"]\n        names[\"driver\"] = self.driver\n        names[\"test\"] = self.test\n        try:\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_get(name):\n                if name in names[\"test\"].context:\n                    return names[\"test\"].context[name]\n                elif name in names[\"test\"].common_params:\n                    return names[\"test\"].common_params[name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"test\"].common_params[name] = val\n                else:\n                    names[\"test\"].context[name] = val\n\n            exec(code)\n            self.test.debugLog(\"成功执行 %s\" % kwargs[\"trans\"])\n        except NoSuchElementException as e:\n            raise e\n        except Exception as e:\n            self.test.errorLog(\"无法执行 %s\" % kwargs[\"trans\"])\n            raise e\n"
  },
  {
    "path": "core/web/find_opt.py",
    "content": "from core.web.driver.browserOpt import Browser\nfrom core.web.driver.pageOpt import Page\nfrom core.web.driver.scenarioOpt import Scenario\nfrom core.web.driver.assertionOpt import Assertion\nfrom core.web.driver.relationOpt import Relation\nfrom core.web.driver.conditionOpt import Condition\n\n\ndef find_browser_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"最大化窗口\")\n    def max_window(test, driver, **kwargs):\n        Browser(test, driver).max_window()\n\n    @keywords(\"最小化窗口\")\n    def min_window(test, driver, **kwargs):\n        Browser(test, driver).min_window()\n\n    @keywords(\"全屏窗口\")\n    def full_window(test, driver, **kwargs):\n        Browser(test, driver).full_window()\n\n    @keywords(\"设置窗口位置\")\n    def set_position_window(test, driver, **kwargs):\n        Browser(test, driver).set_position_window(kwargs[\"data\"][\"x\"], kwargs[\"data\"][\"y\"])\n\n    @keywords(\"设置窗口大小\")\n    def set_size_window(test, driver, **kwargs):\n        Browser(test, driver).set_size_window(kwargs[\"data\"][\"width\"], kwargs[\"data\"][\"height\"])\n\n    @keywords(\"切换窗口\")\n    def switch_to_window(test, driver, **kwargs):\n        Browser(test, driver).switch_to_window(kwargs[\"data\"][\"window\"])\n\n    @keywords(\"关闭窗口\")\n    def close_window(test, driver, **kwargs):\n        Browser(test, driver).close_window()\n\n    @keywords(\"屏幕截图\")\n    def save_screenshot(test, driver, **kwargs):\n        Browser(test, driver).save_screenshot(kwargs[\"data\"][\"name\"])\n\n    @keywords(\"单击跳转新窗口\")\n    def click_to_new_window(test, driver, **kwargs):\n        Browser(test, driver).click_to_new_window(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"返回并关闭当前窗口\")\n    def back_and_close_window(test, driver, **kwargs):\n        Browser(test, driver).back_and_close_window(kwargs[\"data\"][\"window\"])\n\n    @keywords(\"打开网页\")\n    def open_url(test, driver, **kwargs):\n        Browser(test, driver).open_url(kwargs[\"data\"][\"domain\"], kwargs[\"data\"][\"path\"])\n\n    @keywords(\"刷新\")\n    def refresh(test, driver, **kwargs):\n        Browser(test, driver).refresh()\n\n    @keywords(\"后退\")\n    def back(test, driver, **kwargs):\n        Browser(test, driver).back()\n\n    @keywords(\"前进\")\n    def forward(test, driver, **kwargs):\n        Browser(test, driver).forward()\n\n    @keywords(\"强制等待\")\n    def sleep(test, driver, **kwargs):\n        Browser(test, driver).sleep(kwargs[\"data\"][\"second\"])\n\n    @keywords(\"隐式等待\")\n    def implicitly_wait(test, driver, **kwargs):\n        Browser(test, driver).implicitly_wait(kwargs[\"data\"][\"second\"])\n\n    @keywords(\"添加cookie\")\n    def add_cookie(test, driver, **kwargs):\n        Browser(test, driver).add_cookie(kwargs[\"data\"][\"name\"], kwargs[\"data\"][\"value\"])\n\n    @keywords(\"删除cookie\")\n    def delete_cookie(test, driver, **kwargs):\n        Browser(test, driver).delete_cookie(kwargs[\"data\"][\"name\"])\n\n    @keywords(\"删除cookies\")\n    def delete_cookies(test, driver, **kwargs):\n        Browser(test, driver).delete_cookies()\n\n    @keywords(\"执行脚本\")\n    def execute_script(test, driver, **kwargs):\n        Browser(test, driver).execute_script(kwargs[\"data\"][\"script\"], tuple(kwargs[\"data\"][\"arg\"]))\n\n    @keywords(\"执行异步脚本\")\n    def execute_async_script(test, driver, **kwargs):\n        Browser(test, driver).execute_async_script(kwargs[\"data\"][\"script\"], tuple(kwargs[\"data\"][\"arg\"]))\n\n    @keywords(\"自定义\")\n    def custom(test, driver, **kwargs):\n        Browser(test, driver).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_page_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n        return back\n\n    @keywords(\"切换frame\")\n    def switch_frame(test, driver, **kwargs):\n        Page(test, driver).switch_frame(kwargs[\"element\"][\"frame\"])\n\n    @keywords(\"返回默认frame\")\n    def switch_content(test, driver, **kwargs):\n        Page(test, driver).switch_content()\n\n    @keywords(\"返回父级frame\")\n    def switch_parent(test, driver, **kwargs):\n        Page(test, driver).switch_parent()\n\n    @keywords(\"弹出框确认\")\n    def alert_accept(test, driver, **kwargs):\n        Page(test, driver).alert_accept()\n\n    @keywords(\"弹出框输入\")\n    def alert_input(test, driver, **kwargs):\n        Page(test, driver).alert_input(kwargs[\"data\"][\"text\"])\n\n    @keywords(\"弹出框取消\")\n    def alert_cancel(test, driver, **kwargs):\n        Page(test, driver).alert_cancel()\n\n    @keywords(\"鼠标单击\")\n    def click_and_hold(test, driver, **kwargs):\n        Page(test, driver).free_click()\n\n    @keywords(\"清空\")\n    def clear(test, driver, **kwargs):\n        Page(test, driver).clear(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"输入\")\n    def input(test, driver, **kwargs):\n        Page(test, driver).input_text(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"text\"])\n\n    @keywords(\"单击\")\n    def click(test, driver, **kwargs):\n        Page(test, driver).click(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"提交\")\n    def submit(test, driver, **kwargs):\n        Page(test, driver).submit(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"单击保持\")\n    def click_and_hold(test, driver, **kwargs):\n        Page(test, driver).click_and_hold(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"右键点击\")\n    def context_click(test, driver, **kwargs):\n        Page(test, driver).context_click(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"双击\")\n    def double_click(test, driver, **kwargs):\n        Page(test, driver).double_click(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"拖拽\")\n    def drag_and_drop(test, driver, **kwargs):\n        Page(test, driver).drag_and_drop(kwargs[\"element\"][\"startElement\"], kwargs[\"element\"][\"endElement\"])\n\n    @keywords(\"偏移拖拽\")\n    def drag_and_drop_by_offset(test, driver, **kwargs):\n        Page(test, driver).drag_and_drop_by_offset(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"x\"], kwargs[\"data\"][\"y\"])\n\n    @keywords(\"按下键位\")\n    def key_down(test, driver, **kwargs):\n        Page(test, driver).key_down(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"value\"])\n\n    @keywords(\"释放键位\")\n    def key_up(test, driver, **kwargs):\n        Page(test, driver).key_up(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"value\"])\n\n    @keywords(\"鼠标移动到坐标\")\n    def move_by_offset(test, driver, **kwargs):\n        Page(test, driver).move_by_offset(kwargs[\"data\"][\"x\"], kwargs[\"data\"][\"y\"])\n\n    @keywords(\"鼠标移动到元素\")\n    def move_to_element(test, driver, **kwargs):\n        Page(test, driver).move_to_element(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"鼠标元素内偏移\")\n    def move_to_element_with_offset(test, driver, **kwargs):\n        Page(test, driver).move_to_element_with_offset(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"x\"], kwargs[\"data\"][\"y\"])\n\n    @keywords(\"释放点击保持状态\")\n    def release(test, driver, **kwargs):\n        Page(test, driver).release(kwargs[\"element\"][\"element\"])\n\n    @keywords(\"等待元素出现\")\n    def web_driver_wait(test, driver, **kwargs):\n        Page(test, driver).wait_element_appear(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"second\"])\n\n    @keywords(\"等待元素消失\")\n    def web_driver_wait(test, driver, **kwargs):\n        Page(test, driver).wait_element_disappear(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"second\"])\n\n    @keywords(\"自定义\")\n    def custom(test, driver, **kwargs):\n        Page(test, driver).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_assertion_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"断言页面标题\")\n    def assert_page_title(test, driver, **kwargs):\n        return Assertion(test, driver).assert_page_title(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言页面url\")\n    def assert_page_url(test, driver, **kwargs):\n        return Assertion(test, driver).assert_page_url(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言页面源码\")\n    def assert_page_source(test, driver, **kwargs):\n        return Assertion(test, driver).assert_page_source(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素文本\")\n    def assert_ele_text(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_text(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                       kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素tag\")\n    def assert_ele_tag(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_tag(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                      kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素尺寸\")\n    def assert_ele_size(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_size(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                       kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素高度\")\n    def assert_ele_height(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_height(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                         kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素宽度\")\n    def assert_ele_width(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_width(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                        kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素位置\")\n    def assert_ele_location(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_location(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                           kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素X坐标\")\n    def assert_ele_height(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_x(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                    kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素Y坐标\")\n    def assert_ele_y(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_y(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                    kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素属性\")\n    def assert_ele_attribute(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_attribute(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"name\"],\n                                                            kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素是否选中\")\n    def assert_ele_selected(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_selected(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                           kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素是否启用\")\n    def assert_ele_enabled(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_enabled(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                          kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素是否显示\")\n    def assert_ele_displayed(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_displayed(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                            kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素css样式\")\n    def assert_ele_css(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_css(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"name\"],\n                                                      kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言元素是否存在\")\n    def assert_ele_existed(test, driver, **kwargs):\n        return Assertion(test, driver).assert_ele_existed(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                          kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言窗口位置\")\n    def assert_window_position(test, driver, **kwargs):\n        return Assertion(test, driver).assert_window_position(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言窗口X坐标\")\n    def assert_window_x(test, driver, **kwargs):\n        return Assertion(test, driver).assert_window_x(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言窗口Y坐标\")\n    def assert_window_y(test, driver, **kwargs):\n        return Assertion(test, driver).assert_window_y(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言窗口尺寸\")\n    def assert_window_size(test, driver, **kwargs):\n        return Assertion(test, driver).assert_window_size(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言窗口宽度\")\n    def assert_window_width(test, driver, **kwargs):\n        return Assertion(test, driver).assert_window_width(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言窗口高度\")\n    def assert_window_height(test, driver, **kwargs):\n        return Assertion(test, driver).assert_window_height(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言cookies\")\n    def assert_cookies(test, driver, **kwargs):\n        return Assertion(test, driver).assert_cookies(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"断言cookie\")\n    def assert_cookie(test, driver, **kwargs):\n        return Assertion(test, driver).assert_cookie(kwargs[\"data\"][\"name\"], kwargs[\"data\"][\"assertion\"],\n                                                     kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"自定义\")\n    def custom(test, driver, **kwargs):\n        return Assertion(test, driver).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_relation_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"提取页面标题\")\n    def get_page_title(test, driver, **kwargs):\n        Relation(test, driver).get_page_title(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取页面url\")\n    def get_page_url(test, driver, **kwargs):\n        Relation(test, driver).get_page_url(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素文本\")\n    def get_ele_text(test, driver, **kwargs):\n        Relation(test, driver).get_ele_text(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素tag\")\n    def get_ele_tag(test, driver, **kwargs):\n        Relation(test, driver).get_ele_tag(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素尺寸\")\n    def get_ele_size(test, driver, **kwargs):\n        Relation(test, driver).get_ele_size(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素高度\")\n    def get_ele_height(test, driver, **kwargs):\n        Relation(test, driver).get_ele_height(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素宽度\")\n    def get_ele_width(test, driver, **kwargs):\n        Relation(test, driver).get_ele_width(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素位置\")\n    def get_ele_location(test, driver, **kwargs):\n        Relation(test, driver).get_ele_location(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素X坐标\")\n    def get_ele_height(test, driver, **kwargs):\n        Relation(test, driver).get_ele_x(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素Y坐标\")\n    def get_ele_y(test, driver, **kwargs):\n        Relation(test, driver).get_ele_y(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素属性\")\n    def get_ele_attribute(test, driver, **kwargs):\n        Relation(test, driver).get_ele_attribute(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"name\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取元素css样式\")\n    def get_ele_css(test, driver, **kwargs):\n        Relation(test, driver).get_ele_css(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"name\"],\n                                           kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取窗口位置\")\n    def get_window_position(test, driver, **kwargs):\n        Relation(test, driver).get_window_position(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取窗口X坐标\")\n    def get_window_x(test, driver, **kwargs):\n        Relation(test, driver).get_window_x(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取窗口Y坐标\")\n    def get_window_y(test, driver, **kwargs):\n        Relation(test, driver).get_window_y(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取窗口尺寸\")\n    def get_window_size(test, driver, **kwargs):\n        Relation(test, driver).get_window_size(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取窗口宽度\")\n    def get_window_width(test, driver, **kwargs):\n        Relation(test, driver).get_window_width(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取窗口高度\")\n    def get_window_height(test, driver, **kwargs):\n        Relation(test, driver).get_window_height(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取当前窗口句柄\")\n    def get_current_handle(test, driver, **kwargs):\n        Relation(test, driver).get_current_handle(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取所有窗口句柄\")\n    def get_all_handle(test, driver, **kwargs):\n        Relation(test, driver).get_all_handle(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取cookies\")\n    def get_cookies(test, driver, **kwargs):\n        Relation(test, driver).get_cookies(kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"提取cookie\")\n    def get_cookie(test, driver, **kwargs):\n        Relation(test, driver).get_cookie(kwargs[\"data\"][\"name\"], kwargs[\"data\"][\"save_name\"])\n\n    @keywords(\"自定义\")\n    def custom(test, driver, **kwargs):\n        Relation(test, driver).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_condition_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"判断页面标题\")\n    def condition_page_title(test, driver, **kwargs):\n        return Condition(test, driver).condition_page_title(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断页面url\")\n    def condition_page_url(test, driver, **kwargs):\n        return Condition(test, driver).condition_page_url(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断页面源码\")\n    def condition_page_source(test, driver, **kwargs):\n        return Condition(test, driver).condition_page_source(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素文本\")\n    def condition_ele_text(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_text(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                          kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素tag\")\n    def condition_ele_tag(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_tag(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                         kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素尺寸\")\n    def condition_ele_size(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_size(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                          kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素高度\")\n    def condition_ele_height(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_height(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                            kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素宽度\")\n    def condition_ele_width(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_width(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                           kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素位置\")\n    def condition_ele_location(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_location(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                              kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素X坐标\")\n    def condition_ele_height(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_x(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                       kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素Y坐标\")\n    def condition_ele_y(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_y(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                       kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素属性\")\n    def condition_ele_attribute(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_attribute(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"name\"],\n                                                               kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素是否选中\")\n    def condition_ele_selected(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_selected(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                              kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素是否启用\")\n    def condition_ele_enabled(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_enabled(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                             kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素是否显示\")\n    def condition_ele_displayed(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_displayed(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                               kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素css样式\")\n    def condition_ele_css(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_css(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"name\"],\n                                                         kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断元素是否存在\")\n    def condition_ele_existed(test, driver, **kwargs):\n        return Condition(test, driver).condition_ele_existed(kwargs[\"element\"][\"element\"], kwargs[\"data\"][\"assertion\"],\n                                                             kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断窗口位置\")\n    def condition_window_position(test, driver, **kwargs):\n        return Condition(test, driver).condition_window_position(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断窗口X坐标\")\n    def condition_window_x(test, driver, **kwargs):\n        return Condition(test, driver).condition_window_x(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断窗口Y坐标\")\n    def condition_window_y(test, driver, **kwargs):\n        return Condition(test, driver).condition_window_y(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断窗口尺寸\")\n    def condition_window_size(test, driver, **kwargs):\n        return Condition(test, driver).condition_window_size(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断窗口宽度\")\n    def condition_window_width(test, driver, **kwargs):\n        return Condition(test, driver).condition_window_width(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断窗口高度\")\n    def condition_window_height(test, driver, **kwargs):\n        return Condition(test, driver).condition_window_height(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断cookies\")\n    def condition_cookies(test, driver, **kwargs):\n        return Condition(test, driver).condition_cookies(kwargs[\"data\"][\"assertion\"], kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"判断cookie\")\n    def condition_cookie(test, driver, **kwargs):\n        return Condition(test, driver).condition_cookie(kwargs[\"data\"][\"name\"], kwargs[\"data\"][\"assertion\"],\n                                                        kwargs[\"data\"][\"expect\"])\n\n    @keywords(\"自定义\")\n    def custom(test, driver, **kwargs):\n        return Condition(test, driver).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n\n\ndef find_scenario_opt(operate_name: str):\n    function = None\n\n    def keywords(name):\n        def back(func):\n            if name == operate_name:\n                nonlocal function\n                function = func\n\n        return back\n\n    @keywords(\"自定义\")\n    def custom(test, driver, **kwargs):\n        return Scenario(test, driver).custom(**kwargs)\n\n    try:\n        return function\n    except:\n        return None\n"
  },
  {
    "path": "core/web/testcase.py",
    "content": "import re\nfrom selenium import webdriver\nfrom core.template import Template\nfrom core.web.collector import WebOperationCollector\nfrom core.web.teststep import WebTestStep\nfrom tools.utils.utils import get_case_message, handle_operation_data, handle_params_data\n\n\nclass WebTestCase:\n    def __init__(self, test):\n        self.test = test\n        self.context = test.context\n        self.case_message = get_case_message(test.test_data)\n        self.id = self.case_message['caseId']\n        self.name = self.case_message['caseName']\n        setattr(test, 'test_case_name', self.case_message['caseName'])\n        setattr(test, 'test_case_desc', self.case_message['comment'])\n        self.functions = self.case_message['functions']\n        self.params = handle_params_data(self.case_message['params'])\n        test.common_params = self.params\n        self.template = Template(self.test, self.context, self.functions, self.params)\n        self.driver = self.before_execute()\n        self.comp = re.compile(r\"\\{\\{.*?\\}\\}\")\n\n    def execute(self):\n        if self.case_message['optList'] is None:\n            self.after_execute()\n            raise RuntimeError(\"无法获取WEB测试相关数据, 请重试!!!\")\n        try:\n            self.loop_execute(self.case_message['optList'], [])\n        finally:\n            self.after_execute()\n\n    def loop_execute(self, opt_list, skip_opts, step_n=0):\n        while step_n < len(opt_list):\n            opt_content = opt_list[step_n]\n            # 定义收集器\n            collector = WebOperationCollector()\n            step = WebTestStep(self.test, self.driver, self.context, collector)\n            # 定义事务\n            self.test.defineTrans(opt_content[\"operationId\"], opt_content['operationTrans'],\n                                  self.get_opt_content(opt_content['operationElement']), opt_content['operationDesc'])\n            if step_n in skip_opts:\n                self.test.updateTransStatus(3)\n                self.test.debugLog('[{}]操作在条件控制之外不被执行'.format(opt_content['operationTrans']))\n                step_n += 1\n                continue\n            # 收集步骤信息\n            step.collector.collect(opt_content)\n            try:\n                if step.collector.opt_type == \"looper\":\n                    looper_step_num = step.looper_controller(self, opt_list, step_n)\n                    step_n += looper_step_num + 1\n                else:\n                    # 渲染主体\n                    self.render_content(step)\n                    step.execute()\n                    step.assert_controller()\n                    skip_opts.extend(step.condition_controller(step_n))\n                    step_n += 1\n            except Exception as e:\n                if not isinstance(e, AssertionError):\n                    self.test.saveScreenShot(opt_content['operationTrans'], self.driver.get_screenshot_as_png())\n                raise e\n\n    @staticmethod\n    def get_opt_content(elements):\n        content = \"\"\n        if elements is not None:\n            for key, element in elements.items():\n                content = \"%s\\n %s: %s\" % (content, key, element[\"target\"])\n        return content\n\n    def before_execute(self):\n        old_driver = self.test.driver.driver\n        if self.case_message[\"startDriver\"]:\n            # 读取配置\n            opt = webdriver.ChromeOptions()\n            driver_setting = self.render_driver(self.case_message[\"driverSetting\"])\n            if \"arguments\" in driver_setting.keys():\n                for item in driver_setting[\"arguments\"]:\n                    if item[\"value\"] != \"\":\n                        opt.add_argument(item[\"value\"])\n            if \"experimentals\" in driver_setting.keys():\n                for item in driver_setting[\"experimentals\"]:\n                    if item[\"name\"] != \"\" and item[\"value\"] != \"\":\n                        opt.add_experimental_option(item[\"name\"], handle_operation_data(item[\"type\"], item[\"value\"]))\n            if \"extensions\" in driver_setting.keys():\n                for item in driver_setting[\"extensions\"]:\n                    if item[\"value\"] != \"\":\n                        opt.add_encoded_extension(item[\"value\"])\n            if \"files\" in driver_setting.keys():\n                for item in driver_setting[\"files\"]:\n                    if item[\"value\"] != \"\":\n                        opt.add_extension(item[\"value\"])\n            if \"binary\" in driver_setting.keys() and driver_setting[\"binary\"] != \"\":\n                opt.binary_location = driver_setting[\"binary\"]\n            if self.test.driver.browser_opt == \"headless\":\n                opt.add_argument(\"--headless\")\n                opt.add_argument(\"--no-sandbox\")\n            elif self.test.driver.browser_opt == \"remote\":\n                caps = {\n                    'browserName': 'chrome'\n                }\n            else:\n                opt.add_experimental_option('excludeSwitches', ['enable-logging'])\n            if old_driver is not None:\n                old_driver.quit()\n            self.test.driver.driver = None\n            if self.test.driver.browser_opt == \"remote\":\n                return webdriver.Remote(command_executor=self.test.driver.browser_path,\n                                        desired_capabilities=caps, options=opt)\n            else:\n                return webdriver.Chrome(executable_path=self.test.driver.browser_path, options=opt)\n        else:\n            if old_driver is not None:\n                return old_driver\n            else:\n                raise RuntimeError(\"无法找到已启动的浏览器进程 请检查用例开关驱动配置\")\n\n    def after_execute(self):\n        if self.case_message[\"closeDriver\"]:\n            self.driver.quit()\n            self.test.driver.driver = None\n        else:\n            self.test.driver.driver = self.driver\n\n    def render_driver(self, driver_setting):\n        self.template.init(driver_setting)\n        return self.template.render()\n\n    def render_looper(self, looper):\n        self.template.init(looper)\n        _looper = self.template.render()\n        for name, param in _looper.items():\n            if name != \"target\" or name != \"expect\":    # 断言实际值不作数据处理\n                _looper[name] = handle_operation_data(param[\"type\"], param[\"value\"])\n        if \"times\" in _looper:\n            try:\n                times = int(_looper[\"times\"])\n            except:\n                times = 1\n            _looper[\"times\"] = times\n        return _looper\n\n    def render_content(self, step):\n        if step.collector.opt_element is not None:\n            for name, expressions in step.collector.opt_element.items():\n                expression = expressions[1]\n                if self.comp.search(str(expression)) is not None:\n                    self.template.init(expression)\n                    render_value = self.template.render()\n                    expressions = (expressions[0], str(render_value))\n                step.collector.opt_element[name] = expressions\n        if step.collector.opt_data is not None:\n            data = {}\n            for name, param in step.collector.opt_data.items():\n                param_value = param[\"value\"]\n                if isinstance(param_value, str) and self.comp.search(param_value) is not None:\n                    self.template.init(param_value)\n                    param_value = self.template.render()\n                data[name] = handle_operation_data(param[\"type\"], param_value)\n            step.collector.opt_data = data\n\n"
  },
  {
    "path": "core/web/teststep.py",
    "content": "import sys\nfrom datetime import datetime\nfrom core.assertion import LMAssert\nfrom core.web.find_opt import *\n\n\nclass WebTestStep:\n    def __init__(self, test, driver, context, collector):\n        self.test = test\n        self.driver = driver\n        self.context = context\n        self.collector = collector\n        self.result = None\n\n    def execute(self):\n        try:\n            self.test.debugLog('WEB操作[{}]开始'.format(self.collector.opt_name))\n            opt_type = self.collector.opt_type\n            if opt_type == \"browser\":\n                func = find_browser_opt(self.collector.opt_name)\n            elif opt_type == \"page\":\n                func = find_page_opt(self.collector.opt_name)\n            elif opt_type == \"condition\":\n                func = find_condition_opt(self.collector.opt_name)\n            elif opt_type == \"assertion\":\n                func = find_assertion_opt(self.collector.opt_name)\n            elif opt_type == \"relation\":\n                func = find_relation_opt(self.collector.opt_name)\n            else:\n                func = find_scenario_opt(self.collector.opt_name)\n            if func is None:\n                raise NotExistedWebOperation(\"未定义操作\")\n            opt_content = {\n                \"trans\": self.collector.opt_trans,\n                \"code\": self.collector.opt_code,\n                \"element\": self.collector.opt_element,\n                \"data\": self.collector.opt_data\n            }\n            self.result = func(self.test, self.driver, **opt_content)\n            self.log_show()\n        finally:\n            self.test.debugLog('WEB操作[{}]结束'.format(self.collector.opt_name))\n\n    def looper_controller(self, case, opt_list, step_n):\n        \"\"\"循环控制器\"\"\"\n        if self.collector.opt_trans == \"While循环\":\n            loop_start_time = datetime.now()\n            timeout = int(self.collector.opt_data[\"timeout\"][\"value\"])\n            index_name = self.collector.opt_data[\"indexName\"][\"value\"]\n            steps = int(self.collector.opt_data[\"steps\"][\"value\"])\n            index = 0\n            while timeout == 0 or (datetime.now() - loop_start_time).seconds * 1000 < timeout:\n                # timeout为0时可能会死循环 慎重选择\n                self.context[index_name] = index  # 给循环索引赋值第几次循环 母循环和子循环的索引名不应一样\n                _looper = case.render_looper(self.collector.opt_data)  # 渲染循环控制控制器 每次循环都需要渲染\n                index += 1\n                result, _ = LMAssert(_looper['assertion'], _looper['target'], _looper['expect']).compare()\n                if not result:\n                    break\n                _opt_list = opt_list[step_n+1: (step_n + _looper[\"steps\"]+1)]   # 循环操作本身不参与循环 不然死循环\n                case.loop_execute(_opt_list, [])\n            return steps\n        else:\n            _looper = case.render_looper(self.collector.opt_data) # 渲染循环控制控制器 for只需渲染一次\n            for index in range(_looper[\"times\"]):  # 本次循环次数\n                self.context[_looper[\"indexName\"]] = index  # 给循环索引赋值第几次循环 母循环和子循环的索引名不应一样\n                _opt_list = opt_list[step_n+1: (step_n + _looper[\"steps\"]+1)]\n                case.loop_execute(_opt_list, [])\n            return _looper[\"steps\"]\n\n    def assert_controller(self):\n        if self.collector.opt_type == \"assertion\":\n            if self.result[0]:\n                self.test.debugLog('[{}]断言成功: {}'.format(self.collector.opt_trans,\n                                                             self.result[1]))\n            else:\n                self.test.errorLog('[{}]断言失败: {}'.format(self.collector.opt_trans,\n                                                             self.result[1]))\n                self.test.saveScreenShot(self.collector.opt_trans, self.driver.get_screenshot_as_png())\n                if \"continue\" in self.collector.opt_data and self.collector.opt_data[\"continue\"] is True:\n                    try:\n                        raise AssertionError(self.result[1])\n                    except AssertionError:\n                        error_info = sys.exc_info()\n                        self.test.recordFailStatus(error_info)\n                else:\n                    raise AssertionError(self.result[1])\n\n    def condition_controller(self, current):\n        if self.collector.opt_type == \"condition\":\n            offset_true = self.collector.opt_data[\"true\"]\n            if not isinstance(offset_true, int):\n                offset_true = 0\n            offset_false = self.collector.opt_data[\"false\"]\n            if not isinstance(offset_false, int):\n                offset_false = 0\n            if self.result[0]:\n                self.test.debugLog('[{}]判断成功, 执行成功分支: {}'.format(self.collector.opt_name,\n                                                                        self.result[1]))\n                return [current + i for i in range(offset_true + 1, offset_true + offset_false + 1)]\n            else:\n                self.test.errorLog('[{}]判断失败, 执行失败分支: {}'.format(self.collector.opt_name,\n                                                                        self.result[1]))\n                return [current + i for i in range(1, offset_true + 1)]\n        return []\n\n    def log_show(self):\n        msg = \"\"\n        if self.collector.opt_element is not None:\n            for k, v in self.collector.opt_element.items():\n                msg += '元素定位: {}: {}<br>'.format(k, v)\n        if self.collector.opt_data is not None:\n            data_log = '{'\n            for k, v in self.collector.opt_data.items():\n                class_name = type(v).__name__\n                data_log += \"{}: {}, \".format(k, v)\n            if len(data_log) > 1:\n                data_log = data_log[:-2]\n            data_log += '}'\n            msg += '操作数据: {}'.format(data_log)\n        if msg != \"\":\n            msg = '操作信息: <br>' + msg\n            self.test.debugLog(msg)\n\n\nclass NotExistedWebOperation(Exception):\n    \"\"\"未定义的WEB操作\"\"\"\n"
  },
  {
    "path": "lm/lm_api.py",
    "content": "# -*- coding: utf-8 -*-\nimport base64\nimport requests\nimport time\nfrom lm.lm_config import *\nfrom lm.lm_log import DebugLogger, ErrorLogger\n\n\nclass Api(object):\n\n    def __init__(self):\n        config = LMConfig()\n        self.url = config.url[:-1] if config.url.endswith(\"/\") else config.url\n        self.engine = config.engine\n        self.secret = config.secret\n        self.proxy = None\n\n    def request(self, url, data):\n        header = self.load_header()\n        response = requests.post(url=url, json=data, headers=header, proxies=self.proxy, timeout=30)\n        return response\n\n    def download(self, url):\n        header = self.load_header()\n        response = requests.get(url=url, headers=header, proxies=self.proxy, stream=True, timeout=30)\n        return response\n\n    @staticmethod\n    def save_token(token):\n        reader = IniReader()\n        DebugLogger(\"更新token\")\n        reader.modify(\"Header\", \"token\", token)\n\n    @staticmethod\n    def load_header():\n        config = LMConfig()\n        header = config.header\n        return header\n\n\nclass LMApi(Api):\n\n    def apply_token(self):\n        \"\"\"\"申请token\"\"\"\n        url = self.url + \"/openapi/engine/token/apply\"\n        data = {\n            \"engineCode\": self.engine,\n            \"engineSecret\": self.secret,\n            \"timestamp\": int(time.time()),\n        }\n        try:\n            res = self.request(url=url, data=data)\n            if res.status_code == 200:\n                status = res.json()[\"status\"]\n                if status == 0:\n                    token = res.json()[\"data\"]\n                    self.save_token(token)\n                elif status == 2050:\n                    DebugLogger(\"调用申请token接口 引擎id或秘钥错误\")\n                else:\n                    DebugLogger(\"调用申请token接口 token生成失败\")\n            else:\n                DebugLogger(\"调用申请token接口 响应状态为：%s\" % res.status_code)\n        except Exception as e:\n            ErrorLogger(\"调用申请token接口 发生错误 错误信息为：%s\" % e)\n\n    def fetch_task(self):\n        \"\"\"\"获取任务\"\"\"\n        url = self.url + \"/openapi/engine/task/fetch\"\n        for index in range(2):\n            data = {\n                \"engineCode\": self.engine,\n                \"timestamp\": int(time.time())\n            }\n            try:\n                if index > 0:\n                    DebugLogger(\"-------重试调用获取引擎任务接口--------\")\n                res = self.request(url, data)\n                if res.status_code == 200:\n                    status = res.json()[\"status\"]\n                    if status == 0:\n                        return res.json()[\"data\"]\n                    elif status in (2020, 2030, 2040):\n                        DebugLogger(\"token校验错误 重新申请token\")\n                        self.apply_token()\n                        continue\n                    else:\n                        DebugLogger(\"获取引擎任务请求失败\")\n                else:\n                    DebugLogger(\"调用获取引擎任务接口 响应状态为：%s\" % res.status_code)\n            except Exception as e:\n                ErrorLogger(\"调用获取引擎任务接口 发生错误 错误信息为：%s\" % e)\n            break\n\n    def upload_result(self, task_id, data_type, result):\n        \"\"\"\"上传执行结果\"\"\"\n        url = self.url + \"/openapi/engine/result/upload\"\n        for index in range(2):\n            data = {\n                \"engineCode\": self.engine,\n                \"timestamp\": int(time.time()),\n                \"taskId\": task_id,\n                \"caseResultList\": result\n            }\n            try:\n                if index > 0:\n                    DebugLogger(\"-------重试调用上传执行结果接口--------\")\n                res = self.request(url, data)\n                if res.status_code == 200:\n                    status = res.json()[\"status\"]\n                    if status == 0:\n                        return True\n                    elif status in (2020, 2030, 2040):\n                        DebugLogger(\"token校验错误 重新申请token\")\n                        self.apply_token()\n                        continue\n                    else:\n                        DebugLogger(\"上传执行结果请求失败\")\n                else:\n                    DebugLogger(\"调用上传执行结果接口 响应状态为：%s\" % res.status_code)\n            except Exception as e:\n                ErrorLogger(\"调用上传执行结果接口 发生错误 错误信息为：%s\" % e)\n            break\n\n    def complete_task(self, task_id):\n        \"\"\"\"反馈任务结束\"\"\"\n        url = self.url + \"/openapi/engine/task/complete\"\n        for index in range(2):\n            data = {\n                \"engineCode\": self.engine,\n                \"timestamp\": int(time.time()),\n                \"taskId\": task_id\n            }\n            try:\n                if index > 0:\n                    DebugLogger(\"-------重试调用反馈任务结束接口--------\")\n                res = self.request(url, data)\n                if res.status_code == 200:\n                    status = res.json()[\"status\"]\n                    if status == 0:\n                        return True\n                    elif status in (2020, 2030, 2040):\n                        DebugLogger(\"token校验错误 重新申请token\")\n                        self.apply_token()\n                        continue\n                    else:\n                        DebugLogger(\"反馈任务结束请求失败\")\n                else:\n                    DebugLogger(\"调用反馈任务结束接口 响应状态为：%s\" % res.status_code)\n            except Exception as e:\n                ErrorLogger(\"调用反馈任务结束接口 发生错误 错误信息为：%s\" % e)\n            break\n\n    def download_task_file(self, path):\n        \"\"\"下载任务文件\"\"\"\n        url = self.url + path\n        for index in range(2):\n            try:\n                if index > 0:\n                    DebugLogger(\"-------重试调用下载任务文件接口--------\")\n                res = self.download(url)\n                if res.status_code == 200:\n                    if not isinstance(res.content, bytes):\n                        status = res.json()[\"status\"]\n                        if status in (2020, 2030, 2040):\n                            DebugLogger(\"token校验错误 重新申请token\")\n                            self.apply_token()\n                            continue\n                        else:\n                            DebugLogger(\"下载任务文件失败\")\n                    else:\n                        return res\n                else:\n                    DebugLogger(\"调用下载任务文件接口 响应状态为：%s\" % res.status_code)\n            except Exception as e:\n                ErrorLogger(\"调用下载任务文件接口 发生错误 错误信息为：%s\" % e)\n            break\n\n    def download_test_file(self, uuid):\n        \"\"\"下载测试文件\"\"\"\n        url = self.url + \"/openapi/download/test/file/\" + uuid\n        for index in range(2):\n            try:\n                if index > 0:\n                    DebugLogger(\"-------重试调用下载测试文件接口--------\")\n                res = self.download(url)\n                if res.status_code == 200:\n                    if not isinstance(res.content, bytes):\n                        status = res.json()[\"status\"]\n                        if status in (2020, 2030, 2040):\n                            DebugLogger(\"token校验错误 重新申请token\")\n                            self.apply_token()\n                            continue\n                        else:\n                            DebugLogger(\"下载测试文件失败\")\n                    else:\n                        return res\n                else:\n                    DebugLogger(\"调用下载测试文件接口 响应状态为：%s\" % res.status_code)\n            except Exception as e:\n                ErrorLogger(\"调用下载测试文件接口 发生错误 错误信息为：%s\" % e)\n            break\n\n    def upload_screen_shot(self,task_image_path, uuid, log_path):\n        \"\"\"\"上传执行截图\"\"\"\n        url = self.url + \"/openapi/engine/screenshot/upload\"\n        for index in range(2):\n            data = {\n                \"fileName\": \"%s.png\" % uuid,\n                \"engineCode\": self.engine,\n                \"timestamp\": int(time.time())\n            }\n            with open(os.path.join(task_image_path, \"%s.png\" % uuid), \"rb\") as f:\n                file = base64.b64encode(f.read()).decode()\n                data[\"base64String\"] = file\n            try:\n                res = self.request(url, data)\n                if res.status_code == 200:\n                    status = res.json()[\"status\"]\n                    if status == 0:\n                        DebugLogger(\"截图%s上传成功\" % uuid, file_path=log_path)\n                        return True\n                    elif status in (2020, 2030, 2040):\n                        DebugLogger(\"token校验错误 重新申请token\", file_path=log_path)\n                        self.apply_token()\n                        continue\n                    else:\n                        ErrorLogger(\"截图%s上传失败\" % uuid, file_path=log_path)\n                else:\n                    DebugLogger(\"调用上传截图接口 响应状态为：%s\" % res.status_code, file_path=log_path)\n            except Exception as e:\n                ErrorLogger(\"调用上传截图接口 发生错误 错误信息为：%s\" % e, file_path=log_path)\n            break\n        else:\n            ErrorLogger(\"截图%s上传失败\" % uuid, file_path=log_path)\n            return False\n"
  },
  {
    "path": "lm/lm_case.py",
    "content": "# -*- coding: utf-8 -*-\nimport io\nimport os\nimport datetime\nimport sys\nimport time\nimport unittest\nimport traceback\nfrom uuid import uuid1\nfrom core.api.testcase import ApiTestCase\nfrom core.web.testcase import WebTestCase\nfrom core.app.testcase import AppTestCase\nfrom lm.lm_config import IMAGE_PATH, LMConfig\n\n\nclass LMCase(unittest.TestCase):\n\n    def __init__(self, case_name, test_data, case_type=\"API\"):\n        self.test_data = test_data\n        self.trans_list = []\n        self.case_name = case_name\n        self.case_type = case_type\n        unittest.TestCase.__init__(self, case_name)\n\n    def testEntrance(self):\n        if self.case_type == \"API\":\n            ApiTestCase(test=self).execute()\n        elif self.case_type == \"WEB\":\n            WebTestCase(test=self).execute()\n        else:\n            AppTestCase(test=self).execute()\n\n    def doCleanups(self):\n        unittest.TestCase.doCleanups(self)\n        self.handleResult()\n\n    def debugLog(self, log_info):\n        \"\"\"执行日志\"\"\"\n        if len(self.trans_list) > 0:\n            current_time = datetime.datetime.now()\n            log = \"%s - Debug - %s\" % (current_time.strftime('%Y-%m-%d %H:%M:%S.%f'), log_info)\n            if self.trans_list[-1][\"log\"] != \"\":\n                if self.case_type == \"API\":\n                    log = \"<br><br>\" + log\n                else:\n                    log = \"<br>\" + log\n            self.trans_list[-1][\"log\"] = self.trans_list[-1][\"log\"] + log\n\n    def errorLog(self, log_info):\n        \"\"\"错误日志\"\"\"\n        if len(self.trans_list) > 0:\n            current_time = datetime.datetime.now()\n            log = \"%s - Error - %s\" % (current_time.strftime('%Y-%m-%d %H:%M:%S.%f'), log_info)\n            if self.trans_list[-1][\"log\"] != \"\":\n                if self.case_type == \"API\":\n                    log = \"<br><br>\" + log\n                else:\n                    log = \"<br>\" + log\n            self.trans_list[-1][\"log\"] = self.trans_list[-1][\"log\"] + log\n\n    def recordTransDuring(self, during):\n        \"\"\"记录事务时长\"\"\"\n        if len(self.trans_list) > 0:\n            self.trans_list[-1][\"during\"] = during\n\n    def defineTrans(self, id, name, content=\"\", desc=None):\n        \"\"\"定义事务\"\"\"\n        if len(self.trans_list) > 0:\n            self.complete_output()\n            if self.trans_list[-1][\"status\"] == \"\":\n                self.trans_list[-1][\"status\"] = 0\n        trans_dict = {\n            \"id\": id,\n            \"name\": name,\n            \"content\": content,\n            \"description\": desc,\n            \"log\": \"\",\n            \"during\": 0,\n            \"status\": \"\",\n            \"screenShotList\": []\n        }\n        self.trans_list.append(trans_dict)\n\n    def complete_output(self):\n        \"\"\"获取控制台输出\"\"\"\n        stdout_buffer = getattr(self, \"stdout_buffer\", io.StringIO())\n        output = stdout_buffer.getvalue()\n        stdout_buffer.truncate(0)\n        if output:\n            output = output.replace(\"\\n\", \"<br>\")\n            self.debugLog(\"控制台输出:<br> %s\" % output)\n\n    def deleteTrans(self, index):\n        \"\"\"删除事务\"\"\"\n        if len(self.trans_list) > index:\n            del self.trans_list[index]\n\n    def updateTransStatus(self, status):\n        if len(self.trans_list) > 0:\n            self.trans_list[-1][\"status\"] = status\n\n    def recordFailStatus(self, exc_info=None):\n        \"\"\"记录断言失败\"\"\"\n        self._outcome.errors.append((self, exc_info))\n        if len(self.trans_list) > 0:\n            self.trans_list[-1][\"status\"] = 1  # 记录当前事务为失败\n            self.errorLog(str(exc_info[1]))\n\n    def recordErrorStatus(self, exc_info=None):\n        \"\"\"记录程序错误\"\"\"\n        self._outcome.errors.append((self, exc_info))\n        if len(self.trans_list) > 0:\n            self.trans_list[-1][\"status\"] = 2  # 记录当前事务为错误\n            self.errorLog(str(exc_info[1]))\n            if LMConfig().enable_stderr.lower() == \"true\":\n                # 此处可以打印详细报错的代码\n                tb_e = traceback.TracebackException(exc_info[0], exc_info[1], exc_info[2])\n                msg_lines = list(tb_e.format())\n                err_msg = \"程序错误信息: \"\n                for msg in msg_lines:\n                    err_msg = err_msg + \"<br>\" + msg\n                self.errorLog(str(err_msg))\n\n    def saveScreenShot(self, name, screen_shot):\n        \"\"\"保存截图\"\"\"\n        uuid = time.strftime(\"%Y%m%d\") + \"_\" +str(uuid1())\n        task_id = getattr(self, \"task_id\")\n        task_image_path = os.path.join(IMAGE_PATH, task_id)\n        try:\n            filename = \"%s.png\" % uuid\n            if not os.path.exists(task_image_path):\n                os.makedirs(task_image_path)\n            file_path = os.path.join(task_image_path, filename)\n            with open(file_path, 'wb') as f:\n                f.write(screen_shot)\n        except:\n            self.errorLog(\"Fail: Failed to save screen shot %s\" % name)\n        else:\n            if len(self.trans_list) > 0:\n                self.trans_list[-1][\"screenShotList\"].append(uuid)\n\n    def handleResult(self):\n        \"\"\"结果处理\"\"\"\n        if len(self.trans_list) == 0:\n            self.defineTrans(self.case_name.split(\"_\")[1], \"未知\", \"未知\")\n        self.complete_output()\n        isFail = False\n        isError = False\n        error_type = None\n        error_value = None\n        error_tb = None\n        # 处理用例执行过程中的错误和失败 以此来判断用例最终状态\n        for index, (test, exc_info) in enumerate(self._outcome.errors):\n            if exc_info is not None:\n                if issubclass(exc_info[0], AssertionError):\n                    isFail = True\n                    if not isError:  # 默认错误优先级高\n                        error_type = AssertionError\n                        error_value = exc_info[1]\n                        error_tb = exc_info[2]\n                else:\n                    isError = True\n                    error_type = exc_info[0]\n                    error_value = exc_info[1]\n                    error_tb = exc_info[2]\n        # 根据用例原始成功状态来判断最后一个事务是否是成功的\n        if self._outcome.success is True:\n            if self.trans_list[-1][\"status\"] == \"\":  # 如果最后一个事务没有状态，则设为pass\n                self.trans_list[-1][\"status\"] = 0\n            if isError or isFail:  # 有错误或者失败的话用例修改状态\n                self._outcome.errors.clear()\n                self._outcome.errors.append((self, (error_type, error_value, error_tb)))\n                self._outcome.success = False\n        else:\n            # 如果用例原始成功状态为否 则说明最后一个事务是失败或者错误的\n            exc_info = self._outcome.errors[-2][-1]  # 倒数第二个errors是最后一个事务的\n            if issubclass(exc_info[0], AssertionError):\n                self.trans_list[-1][\"status\"] = 1  # 最后一步设为fail\n            else:\n                self.errorLog(str(exc_info[1]))\n                self.trans_list[-1][\"status\"] = 2  # 最后一步设为error\n                if LMConfig().enable_stderr.lower() == \"true\":\n                    # 此处可以打印详细报错的代码\n                    tb_e = traceback.TracebackException(exc_info[0], exc_info[1], exc_info[2])\n                    msg_lines = list(tb_e.format())\n                    err_msg = \"程序错误信息: \"\n                    for msg in msg_lines:\n                        err_msg = err_msg + \"<br>\" + msg\n                    self.errorLog(str(err_msg))\n            self._outcome.errors.clear()\n            self._outcome.errors.append((self, (error_type, error_value, error_tb)))\n"
  },
  {
    "path": "lm/lm_config.py",
    "content": "# -*- coding: utf-8 -*-\nimport os\nimport configparser\n\nBASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\nDATA_PATH = os.path.join(BASE_PATH, \"data\")\nFILE_PATH = os.path.join(BASE_PATH, \"file\")\nLOG_PATH = os.path.join(BASE_PATH, \"log\")\nCONFIG_PATH = os.path.join(BASE_PATH, \"config\", \"config.ini\")\nIMAGE_PATH = os.path.join(BASE_PATH, \"image\")\nBROWSER_PATH = os.path.join(BASE_PATH, \"browser\")\n\n\nclass IniReader:\n\n    def __init__(self, config_ini=CONFIG_PATH):\n        if os.path.exists(config_ini):\n            self.ini_file = config_ini\n        else:\n            raise FileNotFoundError('文件不存在！')\n\n    def data(self, section, option):\n        config = configparser.ConfigParser()\n        config.read(self.ini_file, encoding=\"utf-8\")\n        value = config.get(section, option)\n        return value\n\n    def option(self, section):\n        config = configparser.ConfigParser()\n        config.read(self.ini_file, encoding=\"utf-8\")\n        options = config.options(section)\n        option = {}\n        for key in options:\n            option[key] = self.data(section, key)\n        return option\n\n    def modify(self, section, option, value):\n        config = configparser.ConfigParser()\n        config.read(self.ini_file, encoding=\"utf-8\")\n        config.set(section, option, value)\n        config.write(open(self.ini_file, \"r+\", encoding=\"utf-8\"))\n\n\nclass LMConfig(object):\n    \"\"\"\"配置文件\"\"\"\n    def __init__(self, path=CONFIG_PATH):\n        reader = IniReader(path)\n        self.url = reader.data(\"Platform\", \"url\")\n        self.enable_stderr = reader.data(\"Platform\", \"enable-stderr\")\n        self.engine = reader.data(\"Engine\", \"engine-code\")\n        self.secret = reader.data(\"Engine\", \"engine-secret\")\n        self.header = reader.option(\"Header\")\n        self.browser_opt = reader.data(\"WebDriver\", \"options\")\n        if self.browser_opt == \"remote\" or \"/\" in reader.data(\"WebDriver\", \"path\"):\n            self.browser_path = reader.data(\"WebDriver\", \"path\")\n        else:\n            self.browser_path = os.path.join(BROWSER_PATH, reader.data(\"WebDriver\", \"path\"))\n        self.max_run = reader.data(\"RunSetting\", \"max-run\")\n\n"
  },
  {
    "path": "lm/lm_log.py",
    "content": "# -*- coding: utf-8 -*-\nimport os\nimport logging\nimport threading\nfrom lm.lm_config import LOG_PATH\n\n\nclass LMLogger(object):\n\n    def __init__(self, logger_name='Auto Test'):\n        self.logger = logging.getLogger(logger_name)\n        self.formatter = logging.Formatter(\"%(asctime)s - %(levelname)-4s - %(message)s\")\n        self.logger.setLevel(logging.INFO)\n\n    def get_handler(self, file_path):\n        p, f = os.path.split(file_path)\n        if not (os.path.exists(p)):\n            os.makedirs(p)\n        file_handler = logging.FileHandler(file_path, encoding=\"utf8\")\n        file_handler.setFormatter(self.formatter)\n        return file_handler\n\n\nmy_logger = LMLogger()\ndefault_log_path = os.path.join(LOG_PATH, \"engine_run.log\")\nmy_lock = threading.RLock()\n\n\ndef DebugLogger(log_info, file_path=default_log_path):\n    try:\n        if my_lock.acquire():\n            file_handler = my_logger.get_handler(file_path)\n            my_logger.logger.addHandler(file_handler)\n            my_logger.logger.info(log_info)\n            my_logger.logger.removeHandler(file_handler)\n\n            my_lock.release()\n    except Exception as e:\n        print(\"Failed to record debug log. Reason:\\n %s\" % str(e))\n\n\ndef ErrorLogger(log_info, file_path=default_log_path):\n    try:\n        if my_lock.acquire():\n            file_handler = my_logger.get_handler(file_path)\n            my_logger.logger.addHandler(file_handler)\n            my_logger.logger.error(log_info)\n            my_logger.logger.removeHandler(file_handler)\n\n            my_lock.release()\n    except Exception as e:\n        print(\"Failed to record error log. Reason:\\n %s\" % str(e))\n"
  },
  {
    "path": "lm/lm_report.py",
    "content": "# -*- coding: utf-8 -*-\nimport datetime, time\nimport os\nimport shutil\nfrom lm.lm_api import LMApi\nfrom lm.lm_log import DebugLogger, ErrorLogger\nfrom lm.lm_config import DATA_PATH\n\n\nclass LMReport(object):\n    def __init__(self, message_queue, case_result_queue):\n        self.case_result_queue = case_result_queue\n        self.message_queue = message_queue\n        self.api = LMApi()\n\n    def monitor_result(self):\n        not_send_result = []\n        last_send_time = datetime.datetime.now()\n        while True:\n            try:\n                message = self.case_result_queue.get()\n            except Exception as e:\n                DebugLogger(\"获取执行结果报错 错误信息%s\" % str(e))\n            else:\n                if isinstance(message, str):\n                    if \"run_all_start\" in message:\n                        task_id = message.split(\"--\")[1]\n                        data_type = message.split(\"--\")[-1]\n                        DebugLogger(\"任务执行启动 开始监听执行结果 任务id: %s\" % task_id)\n                    elif \"run_all_stop\" in message:\n                        if len(not_send_result) != 0:\n                            self.api.upload_result(task_id, data_type, not_send_result)\n                        self.post_stop(task_id)  # 执行结束\n                        self.message_queue.put({\"type\": \"completed\", \"data\": task_id})  # 通知任务管理器清空当前执行任务\n                        time.sleep(2)\n                        break\n                    else:  # start_run_index--n\n                        if len(not_send_result) != 0:\n                            self.api.upload_result(task_id, data_type, not_send_result)\n                            not_send_result.clear()\n                        index = int(message.split(\"--\")[-1])\n                        if index > 0:\n                            DebugLogger(\"用例有执行错误 重试执行 任务id: %s\" % task_id)\n                else:\n                    \"\"\"控制请求频率\"\"\"\n                    result = message\n                    not_send_result.append(result)\n                    current_time = datetime.datetime.now()\n                    during = (current_time - last_send_time).seconds\n                    if during < 3:\n                        pass\n                    else:\n                        self.api.upload_result(task_id, data_type, not_send_result)\n                        last_send_time = current_time\n                        not_send_result.clear()\n\n    def post_stop(self, task_id=None):\n        DebugLogger(\"任务执行结束 调用接口通知平台 任务id: %s\" % task_id)\n        self.api.complete_task(task_id)\n        data = os.path.join(DATA_PATH, str(task_id))\n        if os.path.exists(data):\n            try:\n                shutil.rmtree(data)\n            except Exception as e:\n                ErrorLogger(\"删除测试数据失败 失败原因：%s 任务id: %s\" % (str(e), task_id))\n"
  },
  {
    "path": "lm/lm_result.py",
    "content": "# -*- coding: utf-8 -*-\nimport datetime\nimport io\nimport sys\nimport unittest\n\n\nclass LMResult(unittest.TestResult):\n\n    def __init__(self, result, lock, queue):\n        unittest.TestResult.__init__(self)\n        self.stdout_buffer = None\n        self.original_stdout = sys.stdout\n        self.default_result = result\n        self.default_lock = lock\n        self.queue = queue\n        self.result = []\n\n    def startTest(self, test):\n        unittest.TestResult.startTest(self, test)\n        self.setupStdout()\n        test.stdout_buffer = self.stdout_buffer\n        test.start_time = datetime.datetime.now()\n\n    def setupStdout(self):\n        if self.stdout_buffer is None:\n            self.stdout_buffer = io.StringIO()\n\n    def stopTest(self, test):\n        unittest.TestResult.stopTest(self, test)\n        test.stop_time = datetime.datetime.now()\n        if self.default_lock.acquire():\n            status, test_case, error = self.result[-1]\n            case_info = {\n                \"status\": status,\n                \"startTime\": test_case.start_time.timestamp()*1000,\n                \"endTime\": test_case.stop_time.timestamp()*1000,\n                \"collectionId\": test_case.__class__.__doc__.split(\"_\")[-1],\n                \"caseId\": getattr(test, \"case_name\", \" _ \").split(\"_\")[1],\n                \"caseType\": getattr(test, \"case_type\", \"API\"),\n                \"caseName\": getattr(test, \"test_case_name\", \"未知\"),\n                \"caseDesc\": getattr(test, \"test_case_desc\", None),\n                \"index\": int(getattr(test, \"case_name\", \" _0\").split(\"_\")[-1]),\n                \"runTimes\": getattr(test, \"run_index\", 1),\n                \"transactionList\": test_case.trans_list\n            }\n            self.default_result.append(case_info)\n            self.queue.put(case_info)\n            self.default_lock.release()\n\n    def restoreStdout(self):\n        self.stdout_buffer.seek(0)\n        self.stdout_buffer.truncate()\n\n    def addSuccess(self, test):\n        unittest.TestResult.addSuccess(self, test)\n        self.mergeResult(0, test, \"\")\n\n    def addFailure(self, test, err):\n        unittest.TestResult.addFailure(self, test, err)\n        _, _exc_str = self.failures[-1]\n        self.mergeResult(1, test, _exc_str)\n\n    def addError(self, test, err):\n        unittest.TestResult.addError(self, test, err)\n        _, _exc_str = self.errors[-1]\n        self.mergeResult(2, test, _exc_str)\n\n    def addSkip(self, test, reason):\n        unittest.TestResult.addSkip(self, test, reason)\n        self.mergeResult(3, test, reason)\n\n    def mergeResult(self, n, test, e):\n        self.result.append((n, test, e))\n"
  },
  {
    "path": "lm/lm_run.py",
    "content": "# -*- coding: utf-8 -*-\nimport unittest, threading\nfrom lm import lm_case, lm_result\nfrom lm.lm_log import ErrorLogger\nfrom lm.lm_config import LMConfig\n\n\nclass LMRun(object):\n    def __init__(self, plan_tuple, run_index, default_result, default_lock, queue):\n        self.plan_tuple = plan_tuple\n        self.run_index = run_index\n        self.default_result = default_result\n        self.default_lock = default_lock\n        self.queue = queue\n\n    def run_test(self):\n        suite = unittest.TestSuite()\n        for case in self.plan_tuple:\n            cls_name = case[\"test_class\"]\n            try:\n                cls = eval(cls_name)\n            except:\n                cls = type(cls_name, (lm_case.LMCase,), {'__doc__': cls_name})\n            case_name = case[\"test_case\"]\n            case_type = case[\"test_type\"]\n            setattr(cls, case_name, lm_case.LMCase.testEntrance)\n            case_data = case[\"test_data\"]\n            test_case = cls(case_name, case_data, case_type)\n            test_case.task_id = case[\"task_id\"]\n            test_case.driver = case[\"driver\"]\n            test_case.session = case[\"session\"]\n            test_case.context = case[\"context\"]\n            test_case.run_index = self.run_index\n            suite.addTest(test_case)\n\n        result = lm_result.LMResult(self.default_result, self.default_lock, self.queue)\n\n        try:\n            suite(result)\n            # 执行测试用例\n        except Exception as ex:\n            ErrorLogger(\"Failed to run test(RunTime:run%s & ThreadName:%s), Error info:%s\" %\n                        (self.run_index, threading.current_thread().name, ex))\n"
  },
  {
    "path": "lm/lm_setting.py",
    "content": "# -*- coding: utf-8 -*-\nimport os\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom requests import Session\nimport zipfile\nfrom lm.lm_run import LMRun\nfrom lm.lm_log import DebugLogger, ErrorLogger\nfrom lm.lm_config import DATA_PATH, LMConfig\nfrom lm.lm_api import LMApi\n\n\nclass LMSetting(object):\n    def __init__(self, task):\n        self.task = task\n        self.data_path = DATA_PATH\n        self.config = LMConfig()\n\n    def data_pull(self):\n        data_url = self.task[\"downloadUrl\"]\n        if not os.path.exists(self.data_path):\n            os.makedirs(self.data_path)\n        try:\n            file = LMApi().download_task_file(data_url)\n        except Exception as e:\n            ErrorLogger(\"数据拉取失败 错误信息: %s 任务id: %s\" % (str(e), self.task[\"taskId\"]))\n            return None\n        else:\n            file_path = os.path.join(self.data_path, str(self.task[\"taskId\"]) + \".zip\")\n            with open(file_path, 'wb+') as f:\n                for chunk in file.iter_content(chunk_size=1024):\n                    if chunk:\n                        f.write(chunk)\n            f.close()\n            DebugLogger(\"数据拉取成功 任务id: %s\" % self.task[\"taskId\"])\n            return file_path\n\n    def file_unzip(self, file_path):\n        r = zipfile.is_zipfile(file_path)\n        if r:\n            with zipfile.ZipFile(file_path, 'r') as fz:\n                for file in fz.namelist():\n                    fz.extract(file, self.data_path)\n        os.remove(file_path)\n\n    def task_analysis(self):\n        test_plan = {}\n        if self.task[\"taskType\"] != 'debug':\n            file_path = self.data_pull()\n            if file_path is not None:\n                self.file_unzip(file_path)\n            for collection_map in self.task[\"testCollectionList\"]:\n                collection = collection_map[\"collectionId\"]\n                test_case_list = collection_map[\"testCaseList\"]\n                session = LMSession()\n                driver = LMDriver()\n                context = dict()\n                for case in test_case_list:\n                    test_case = {\n                        \"driver\": driver,\n                        \"session\": session,\n                        \"context\": context,\n                        \"task_id\": self.task[\"taskId\"],\n                        \"test_type\": case[\"caseType\"],\n                        \"test_class\": \"class_\" + collection,\n                        \"test_case\": \"case_%s_%s\" % (case[\"caseId\"], case[\"index\"]),\n                        \"test_data\": os.path.join(self.data_path, self.task[\"taskId\"], collection, case[\"caseId\"] + \".json\")\n                    }\n                    if collection not in test_plan.keys():\n                        test_plan[collection] = []\n                    test_plan[collection].append(test_case)\n        else:\n            collection_map = self.task[\"testCollectionList\"][0]\n            collection = collection_map[\"collectionId\"]\n            session = LMSession()\n            driver = LMDriver()\n            context = dict()\n            test_case = {\n                \"driver\": driver,\n                \"session\": session,\n                \"context\": context,\n                \"task_id\": self.task[\"taskId\"],\n                \"test_type\": collection_map[\"testCaseList\"][0][\"caseType\"],\n                \"test_class\": \"class_\" + collection,\n                \"test_case\": \"case_%s_%s\" % (collection_map[\"testCaseList\"][0][\"caseId\"], collection_map[\"testCaseList\"][0][\"index\"]),\n                \"test_data\": self.task[\"debugData\"]\n            }\n            test_plan[collection] = [test_case]\n        return test_plan\n\n    def create_thread(self, plan, queue, current_exec_status):\n        runTime = 1\n        if self.task[\"reRun\"]:\n            runTime = 2\n        task_id = self.task[\"taskId\"]\n        max_thread = self.task[\"maxThread\"]\n        queue.put(\"run_all_start--%s\" % task_id)\n        for index in range(runTime):\n            if index == 0:\n                test_plan = plan\n            else:\n                test_plan = self.read_fail_case(test_plan, default_result)\n            default_result = []\n            if len(test_plan) > 0:\n                queue.put(\"start_run_index--%s\" % index)\n                default_lock = threading.RLock()\n                # 进行线程池管理执行 设置最大并发\n                with ThreadPoolExecutor(max_workers=max_thread) as t:\n                    executors = [t.submit(LMRun(test_case_list, index + 1, default_result, default_lock,\n                                                queue).run_test, ) for test_case_list in test_plan.values()]\n                    as_completed(executors)\n\n        queue.put(\"run_all_stop--%s\" % task_id)\n        current_exec_status.value = 1\n\n    @staticmethod\n    def read_fail_case(test_plan, result):\n        new_test_plan = {}\n        for collection, test_case_list in test_plan.items():\n            for test in test_case_list:\n                case_id = test[\"test_case\"].split(\"_\")[1]\n                index = test[\"test_case\"].split(\"_\")[-1]\n                for case in result:\n                    if case[\"collectionId\"] == collection and case[\"caseId\"] == case_id and case[\"index\"] == int(index):\n                        if case[\"status\"] in (1, 2):\n                            if collection not in new_test_plan:\n                                new_test_plan[collection] = []\n                            new_test_plan[collection].append(test)\n                        result.remove(case)\n                        break\n        return new_test_plan\n\n    \nclass LMSession(object):\n    \"\"\"API测试专用\"\"\"\n    def __init__(self):\n        self.session = Session()\n\n\nclass LMDriver(object):\n    \"\"\"WEB测试专用\"\"\"\n    def __init__(self):\n        self.driver = None\n        self.config = LMConfig()\n        self.browser_opt = self.config.browser_opt\n        self.browser_path = self.config.browser_path\n\n"
  },
  {
    "path": "lm/lm_start.py",
    "content": "# -*- coding: utf-8 -*-\nimport threading\nfrom multiprocessing import Process, Queue, Value\nimport time, os\nfrom lm.lm_api import LMApi\nfrom lm.lm_setting import LMSetting\nfrom lm.lm_report import LMReport\nfrom lm.lm_log import DebugLogger, ErrorLogger\nfrom lm.lm_config import LOG_PATH, IMAGE_PATH, LMConfig\nfrom lm.lm_upload import LMUpload\nimport psutil\nfrom lm.lm_ws import Client\n\n\nclass LMStart(object):\n\n    def __init__(self):\n        self.api = LMApi()\n        self.config = LMConfig()\n        self.exec_processes = {}\n\n    def main(self):\n        \"\"\"\"启动入口\"\"\"\n        message_queue = Queue()     # 消息队列\n        status_thread = threading.Thread(target=self.send_heartbeat, args=(message_queue,))\n        status_thread.start()   # 启动心跳链接\n        task_queue = Queue()    # 任务队列\n        task_thread = threading.Thread(target=self.fetch_task, args=(task_queue,))\n        task_thread.start()  # 启动拉取任务 初始化一次 避免任务遗漏\n        monitor_thread = threading.Thread(target=self.monitor_message, args=(message_queue, task_queue))\n        monitor_thread.start()     # 启动消息监听\n        while True:\n            try:\n                task = task_queue.get(True, 1)\n            except:\n                continue\n            else:\n                DebugLogger(\"接受任务成功 启动执行进程 任务id: %s\" % task[\"taskId\"])\n                case_result_queue = Queue()\n                current_exec_status = Value(\"i\", 0)  # 0 执行中、 1 执行结束\n                run_process = Process(target=self.run_test, args=(task, case_result_queue, current_exec_status))\n                run_process.start()\n                report_process = Process(target=self.push_result, args=(message_queue, case_result_queue))\n                report_process.start()\n                upload_process = Process(target=self.upload_image, args=(task, current_exec_status))\n                upload_process.start()\n                self.exec_processes[task[\"taskId\"]] = [run_process, report_process, upload_process]     # 保存当前进程\n\n    def send_heartbeat(self, queue):\n        while True:\n            log_path = os.path.join(LOG_PATH, \"engine_status.log\")\n            domain = self.config.url[:-1] if self.config.url.endswith(\"/\") else self.config.url\n            url = domain.replace(\"http\", \"ws\") + \"/websocket/engine/heartbeat?engineCode={}&engineSecret={}\". \\\n                format(self.config.engine, self.config.secret)\n            try:\n                ws = Client(url, queue)\n                ws.connect()\n                while True:\n                    time.sleep(30)\n                    ws.send(bytes(0))   # 每隔30秒更新心跳\n                    DebugLogger(\"-------------------------------------------------\", file_path=log_path)\n                    DebugLogger(\"心跳更新成功\", file_path=log_path)\n                    DebugLogger(\"-------------------------------------------------\", file_path=log_path)\n            except KeyboardInterrupt:\n                ws.close()\n            except Exception as e:\n                DebugLogger(\"-------------------------------------------------\", file_path=log_path)\n                ErrorLogger(\"心跳连接失败 1秒钟后重试 失败原因%s\" % e, file_path=log_path)\n                DebugLogger(\"-------------------------------------------------\", file_path=log_path)\n            time.sleep(1)\n\n    def fetch_task(self, queue):\n        while True:\n            if len(self.exec_processes) < int(self.config.max_run):\n                task = self.api.fetch_task()\n                if task:\n                    self.exec_processes[task[\"taskId\"]] = []\n                    DebugLogger(\"引擎获取任务成功 任务id: %s\" % (task[\"taskId\"]))\n                    queue.put(task)\n                else:   # 没有任务 停止获取\n                    break\n            else:\n                time.sleep(3)\n\n    def monitor_message(self, message_queue, task_queue):\n        while True:\n            try:\n                message = message_queue.get(True, 0.1)\n            except:\n                continue\n            else:\n                if message[\"type\"] == \"start\":\n                    task_thread = threading.Thread(target=self.fetch_task, args=(task_queue,))\n                    task_thread.start()\n                elif message[\"type\"] == \"stop\":\n                    if message[\"data\"] in self.exec_processes:\n                        processes = self.exec_processes[message[\"data\"]]\n                        for process in processes:\n                            if process.is_alive():\n                                process.terminate()\n                        del self.exec_processes[message[\"data\"]]\n                        DebugLogger(\"引擎终止任务成功 任务id: %s\" % message[\"data\"])\n                elif message[\"type\"] == \"stopAll\":\n                    for task_id, processes in self.exec_processes.items():\n                        for process in processes:\n                            if process.is_alive():\n                                process.terminate()\n                        DebugLogger(\"引擎终止任务成功 任务id: %s\" % task_id)\n                    self.exec_processes.clear()\n                else:  # completed\n                    if message[\"data\"] in self.exec_processes:\n                        del self.exec_processes[message[\"data\"]]\n\n    @staticmethod\n    def run_test(task, queue, current_exec_status):\n        s = LMSetting(task)\n        plan = s.task_analysis()\n        s.create_thread(plan, queue, current_exec_status)\n\n    @staticmethod\n    def push_result(message_queue, case_result_queue):\n        report = LMReport(message_queue, case_result_queue)\n        report.monitor_result()\n\n    @staticmethod\n    def upload_image(task, current_exec_status):\n        log_path = os.path.join(LOG_PATH, \"engine_image.log\")\n        current_process = psutil.Process(os.getpid())\n        task_image_path = os.path.join(IMAGE_PATH, task[\"taskId\"])\n        if not os.path.exists(task_image_path):\n            os.makedirs(task_image_path)\n        while True:\n            if current_process.parent() is None:\n                current_process.kill()\n            files = os.listdir(task_image_path)\n            if len(files) > 0:\n                DebugLogger(\"-------------------------------------------------\", file_path=log_path)\n                DebugLogger(\"上传截图\", file_path=log_path)\n                LMUpload(files, log_path).set_upload(task_image_path)\n                DebugLogger(\"-------------------------------------------------\", file_path=log_path)\n            else:\n                if current_exec_status.value:\n                    os.rmdir(task_image_path)\n                    current_process.terminate()\n            time.sleep(1)\n"
  },
  {
    "path": "lm/lm_upload.py",
    "content": "# -*- coding: utf-8 -*-\nimport os\nimport threading\nfrom lm.lm_api import LMApi\n\n\nclass LMUpload(object):\n\n    def __init__(self, files, log_path):\n        self.files = files\n        self.log_path = log_path\n        self.api = LMApi()\n\n    def set_upload(self, task_image_path):\n        threads = []\n        for file in self.files:\n            if file.endswith(\".png\"):\n                uuid = file[:-4]\n                thread = threading.Thread(target=self.upload, args=(task_image_path, uuid, file))\n                threads.append(thread)\n            else:\n                os.remove(os.path.join(task_image_path, file))\n        else:\n            for t in threads:\n                t.start()\n            for t in threads:\n                t.join()\n\n    def upload(self, task_image_path, uuid, file):\n        try:\n            self.api.upload_screen_shot(task_image_path, uuid, self.log_path)\n            os.remove(os.path.join(task_image_path, file))\n        except:\n            pass\n\n"
  },
  {
    "path": "lm/lm_ws.py",
    "content": "import json\nimport os\nfrom ws4py.client.threadedclient import WebSocketClient\nfrom lm.lm_config import LOG_PATH\nfrom lm.lm_log import DebugLogger\n\n\nclass Client(WebSocketClient):\n\n    def __init__(self, url, queue):\n        self.queue = queue\n        WebSocketClient.__init__(self, url)\n        self.log_path = os.path.join(LOG_PATH, \"engine_status.log\")\n\n    def opened(self):\n        DebugLogger(\"-------------------------------------------------\", file_path=self.log_path)\n        DebugLogger(\"心跳连接成功\", file_path=self.log_path)\n        DebugLogger(\"-------------------------------------------------\", file_path=self.log_path)\n\n    def closed(self, code, reason=None):\n        DebugLogger(\"-------------------------------------------------\", file_path=self.log_path)\n        DebugLogger(\"心跳关闭 原因%s %s\" % (code, reason), file_path=self.log_path)\n        DebugLogger(\"-------------------------------------------------\", file_path=self.log_path)\n\n    def received_message(self, resp):\n        self.queue.put(json.loads(str(resp)))\n\n"
  },
  {
    "path": "requirements.txt",
    "content": "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.2\nply==3.11\npsutil==5.8.0\npypinyin==0.40.0\npython-dateutil==2.8.1\nrequests==2.24.0\nselenium==3.141.0\nsix==1.15.0\ntext-unidecode==1.3\nurllib3==1.25.10\nuiautomator2==2.16.21\nfacebook-wda==1.4.6\npymssql==2.2.7\nPyMySQL==1.0.2\ncx_Oracle==8.3.0\npsycopg2==2.9.5\nws4py~=0.5.1\nwebsocket~=0.2.1\n"
  },
  {
    "path": "startup.py",
    "content": "from lm.lm_start import LMStart\n\n\n__version__ = \"1.4.1\"\n\n\nif __name__ == '__main__':\n    print(\"-------------------------------------------------\")\n    print(\"当前所属版本号: %s\" % __version__)\n    print(\"流马测试引擎已启动\")\n    print(\"-------------------------------------------------\")\n    LMStart().main()\n"
  },
  {
    "path": "tools/funclib/__init__.py",
    "content": "from .load_faker import CustomFaker\nimport time\n\n\ndef get_func_lib(test=None, lm_func=None, context=None, params=None):\n    temp = {\n        \"context\": context,\n        \"params\": params\n    }\n    faker = CustomFaker(locale='zh_cn', package='provider', test=test, lm_func=lm_func, temp=temp)\n    CustomFaker.seed(str(time.time()))\n    return faker\n\n"
  },
  {
    "path": "tools/funclib/load_faker.py",
    "content": "import os\nfrom faker import Faker\nfrom importlib import import_module, reload\nimport sys\nfrom faker.providers import BaseProvider\nfrom tools.funclib.params_enum import PARAMS_ENUM\n\n\nclass CustomFaker(Faker):\n    def __init__(self, package='provider', test=None, lm_func=None, temp=None, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if lm_func is None:\n            lm_func = []\n        self.package = package\n        self.test = test\n        self.print = print\n        self.lm_func = lm_func\n        self.temp = temp\n        self.func_param = PARAMS_ENUM\n        self._load_module()\n        self._load_lm_func()\n\n    def __call__(self, name, *args, **kwargs):\n        return getattr(self, name)(*args, **kwargs)\n\n    def _read_module(self):\n        module_path = os.path.join(os.path.dirname(__file__), self.package)\n        module_list = []\n        for file_name in os.listdir(module_path):\n            if file_name[-2:] == \"py\":\n                module_name = __package__ + \".\" + self.package + \".\" + file_name[0:-3]\n                module_list.append(module_name)\n        return module_list\n\n    def _load_module(self):\n        for name in self._read_module():\n            if name not in sys.modules:\n                module = import_module(name)\n            else:\n                module = sys.modules.get(name)\n                reload(module)\n            for value in module.__dict__.values():\n                if type(value) is type and BaseProvider in value.__bases__:\n                    self.add_provider(value)\n\n    def _load_lm_func(self):\n        for custom in self.lm_func:\n            func = self._lm_custom_func(custom[\"code\"], custom[\"params\"][\"names\"], self.test, self.temp)\n            params = []\n            for value in custom[\"params\"][\"types\"]:\n                if value == \"Int\":\n                    params.append(int)\n                elif value == \"Float\":\n                    params.append(float)\n                elif value == \"Boolean\":\n                    params.append(bool)\n                elif value == \"Bytes\":\n                    params.append(bytes)\n                elif value == \"JSONObject\":\n                    params.append(dict)\n                elif value == \"JSONArray\":\n                    params.append(list)\n                elif value == \"Other\":\n                    params.append(None)\n                else:\n                    params.append(str)\n            self.func_param[custom[\"name\"]] = params\n            setattr(self, custom[\"name\"], func)\n\n    def _lm_custom_func(self, code, params, test, temp):\n        def func(*args):\n            def print(*args, sep=' ', end='\\n', file=None, flush=False):\n                if file is None or file in (sys.stdout, sys.stderr):\n                    file = names[\"_test\"].stdout_buffer\n                self.print(*args, sep=sep, end=end, file=file, flush=flush)\n\n            def sys_return(res):\n                names[\"_exec_result\"] = res\n\n            def sys_get(name):\n                if name in names[\"_test_context\"]:\n                    return names[\"_test_context\"][name]\n                elif name in names[\"_test_params\"]:\n                    return names[\"_test_params\"][name]\n                else:\n                    raise KeyError(\"不存在的公共参数或关联变量: {}\".format(name))\n\n            def sys_put(name, val, ps=False):\n                if ps:\n                    names[\"_test_params\"][name] = val\n                else:\n                    names[\"_test_context\"][name] = val\n\n            names = locals()\n            names[\"_test_context\"] = temp[\"context\"]\n            names[\"_test_params\"] = temp[\"params\"]\n            names[\"_test\"] = test\n            for index, value in enumerate(params):\n                names[value] = args[index]\n            exec(code)\n            return names[\"_exec_result\"]\n        return func\n"
  },
  {
    "path": "tools/funclib/params_enum.py",
    "content": "PARAMS_ENUM = {\n    \"bothify\": [str, str],\n    \"lexify\": [str, str],\n    \"numerify\": [str],\n    \"random_int\": [int, int, int],\n    \"random_number\": [int, bool],\n    \"country_code\": [str],\n    \"ean\": [int],\n    \"localized_ean\": [int],\n    \"color\": [str, str, str],\n    \"credit_card_full\": [str],\n    \"credit_card_number\": [str],\n    \"credit_card_provider\": [str],\n    \"credit_card_security_code\": [str],\n    \"date\": [str],\n    \"time\": [str],\n    \"file_extension\": [str],\n    \"file_name\": [str, str],\n    \"file_path\": [int, str, str],\n    \"mime_type\": [str],\n    \"unix_device\": [str],\n    \"unix_partition\": [str],\n    \"domain_name\": [int],\n    \"email\": [str],\n    \"hostname\": [int],\n    \"image_url\": [int, int],\n    \"ipv4\": [bool, str, bool],\n    \"ipv4_private\": [bool, str],\n    \"ipv4_public\": [bool, str],\n    \"ipv6\": [bool],\n    \"uri_path\": [int],\n    \"isbn10\": [str],\n    \"isbn13\": [str],\n    \"paragraph\": [int, bool],\n    \"paragraphs\": [int],\n    \"sentence\": [int, bool],\n    \"sentences\": [int],\n    \"text\": [int],\n    \"texts\": [int, int],\n    \"word\": [int],\n    \"words\": [int],\n    \"password\": [int, bool, bool, bool, bool],\n    \"pyfloat\": [int, int, bool],\n    \"loadfile\": [str],\n    \"savefile\": [str],\n    \"b64encode_str\": [str],\n    \"b64encode_bytes\": [bytes],\n    \"b64encode_file\": [str],\n    \"b64decode_toStr\": [str],\n    \"b64decode_toBytes\": [str],\n    \"arithmetic\": [str],\n    \"current_time\": [str],\n    \"year_shift\": [float, str],\n    \"month_shift\": [float, str],\n    \"week_shift\": [float, str],\n    \"date_shift\": [float, str],\n    \"hour_shift\": [float, str],\n    \"minute_shift\": [float, str],\n    \"second_shift\": [float, str],\n    \"lenof\": [list],\n    \"indexof\": [list, int],\n    \"keyof\": [dict, str],\n    \"pinyin\": [str],\n    \"substing\": [str, int, int],\n    \"extract\": [str],\n    \"replace\": [str, str, str],\n    \"map_dumps\": [dict],\n    \"array_dumps\": [list],\n}\n"
  },
  {
    "path": "tools/funclib/provider/lm_provider.py",
    "content": "import os\nfrom functools import reduce\nfrom faker.providers import BaseProvider\nimport time\nfrom lm.lm_api import LMApi\nfrom pypinyin import lazy_pinyin\nimport base64\nimport datetime\nimport json\nfrom dateutil.relativedelta import relativedelta\nfrom lm.lm_config import FILE_PATH\n\n\nclass LiuMaProvider(BaseProvider):\n\n    @staticmethod\n    def loadfile(uuid):\n        try:\n            res = LMApi().download_test_file(uuid)\n        except:\n            raise Exception(\"拉取测试文件失败\")\n        else:\n            return res.content\n\n    @staticmethod\n    def savefile(uuid):\n        try:\n            res = LMApi().download_test_file(uuid)\n        except:\n            raise Exception(\"拉取测试文件失败\")\n        else:\n            file_name = res.headers.get(\"Content-Disposition\").split(\"=\")[1][1:-1]\n            dir_path = os.path.join(FILE_PATH, uuid)\n            file_path = os.path.join(dir_path, file_name)\n            if not os.path.exists(dir_path):\n                os.makedirs(dir_path)\n                with open(file_path, 'wb+') as f:\n                    for chunk in res.iter_content(chunk_size=1024):\n                        if chunk:\n                            f.write(chunk)\n                f.close()\n            return file_path\n\n    @staticmethod\n    def b64encode_str(s: str):\n        return base64.b64encode(s.encode('utf-8')).decode()\n\n    @staticmethod\n    def b64encode_bytes(s: bytes):\n        return base64.b64encode(s).decode()\n\n    def b64encode_file(self, uuid):\n        content = self.loadfile(uuid)\n        return base64.b64encode(content).decode()\n\n    @staticmethod\n    def b64decode_toStr(s: str):\n        return base64.b64decode(s).decode()\n\n    @staticmethod\n    def b64decode_toBytes(s: str):\n        return base64.b64decode(s)\n\n    @staticmethod\n    def arithmetic(expression: str):\n        try:\n            return eval(expression)\n        except Exception:\n            raise Exception(\"四则运算表达式错误:%s\" % expression)\n\n    @staticmethod\n    def current_time(s: str = '%Y-%m-%d'):\n        if s.lower() == \"none\":\n            return int(time.time() * 1000)\n        return time.strftime(s)\n\n    @staticmethod\n    def year_shift(shift, s: str = '%Y-%m-%d'):\n        now_date = datetime.datetime.now()\n        shift_date = now_date + relativedelta(years=shift)\n        if s.lower() == \"none\":\n            return int(shift_date.timestamp() * 1000)\n        return shift_date.strftime(s)\n\n    @staticmethod\n    def month_shift(shift, s: str = '%Y-%m-%d'):\n        now_date = datetime.datetime.now()\n        shift_date = now_date + relativedelta(months=shift)\n        if s.lower() == \"none\":\n            return int(shift_date.timestamp() * 1000)\n        return shift_date.strftime(s)\n\n    @staticmethod\n    def week_shift(shift, s: str = '%Y-%m-%d'):\n        now_date = datetime.datetime.now()\n        delta = datetime.timedelta(weeks=shift)\n        shift_date = now_date + delta\n        if s.lower() == \"none\":\n            return int(shift_date.timestamp() * 1000)\n        return shift_date.strftime(s)\n\n    @staticmethod\n    def date_shift(shift, s: str = '%Y-%m-%d'):\n        now_date = datetime.datetime.now()\n        delta = datetime.timedelta(days=shift)\n        shift_date = now_date + delta\n        if s.lower() == \"none\":\n            return int(shift_date.timestamp() * 1000)\n        return shift_date.strftime(s)\n\n    @staticmethod\n    def hour_shift(shift, s: str = '%Y-%m-%d %H:%M:%S'):\n        now_date = datetime.datetime.now()\n        delta = datetime.timedelta(hours=shift)\n        shift_date = now_date + delta\n        if s.lower() == \"none\":\n            return int(shift_date.timestamp() * 1000)\n        return shift_date.strftime(s)\n\n    @staticmethod\n    def minute_shift(shift, s: str = '%Y-%m-%d %H:%M:%S'):\n        now_date = datetime.datetime.now()\n        delta = datetime.timedelta(minutes=shift)\n        shift_date = now_date + delta\n        if s.lower() == \"none\":\n            return int(shift_date.timestamp() * 1000)\n        return shift_date.strftime(s)\n\n    @staticmethod\n    def second_shift(shift, s: str = '%Y-%m-%d %H:%M:%S'):\n        now_date = datetime.datetime.now()\n        delta = datetime.timedelta(seconds=shift)\n        shift_date = now_date + delta\n        if s.lower() == \"none\":\n            return int(shift_date.timestamp() * 1000)\n        return shift_date.strftime(s)\n\n    @staticmethod\n    def lenof(array):\n        return len(array)\n\n    @staticmethod\n    def indexof(array, index):\n        return array[index]\n\n    @staticmethod\n    def keyof(map, key):\n        return map[key]\n\n    @staticmethod\n    def pinyin(cname: str):\n        return reduce(lambda x, y: x + y, lazy_pinyin(cname))\n\n    @staticmethod\n    def substing(s, start: int=0, end: int=-1):\n        return s[start:end]\n\n    @staticmethod\n    def extract(data):\n        return data\n\n    @staticmethod\n    def replace(s, old, new):\n        return s.replace(old, new)\n\n    @staticmethod\n    def map_dumps(tar):\n        return json.dumps(tar)\n\n    @staticmethod\n    def array_dumps(tar):\n        return json.dumps(tar)\n"
  },
  {
    "path": "tools/utils/sql.py",
    "content": "import decimal\nimport pymssql as mssql\nimport pymysql as mysql\nimport psycopg2 as pgsql\nimport cx_Oracle as oracle\n\n\nclass SQLConnect:\n\n    def __init__(self, tpz, host, port, db, user, password):\n        self.tpz = tpz\n        self.host = host\n        self.port = int(port)\n        self.db = db\n        self.user = user\n        self.pwd = password\n        self.conn = None\n\n    def connect(self):\n        \"\"\"得到连接信息\"\"\"\n        if self.tpz == \"mysql\":\n            self.conn = mysql.connect(host=self.host, user=self.user,\n                                      password=self.pwd, database=self.db, port=self.port, charset='utf8')\n        elif self.tpz == \"mssql\":\n            self.conn = mssql.connect(server=self.host, user=self.user,\n                                      password=self.pwd, database=self.db, port=self.port, charset='utf8')\n        elif self.tpz == \"pgsql\":\n            self.conn = pgsql.connect(host=self.host, user=self.user,\n                                      password=self.pwd, database=self.db, port=self.port)\n        elif self.tpz == \"oracle\":\n            try:\n                self.conn = oracle.connect(self.user, self.pwd, f\"{self.host}:{self.port}/{self.db}\")\n            except:\n                sn = oracle.makedsn(self.host, self.port, sid=self.db)\n                self.conn = oracle.connect(self.user, self.pwd, sn)\n        else:\n            raise TypeError(\"不支持的数据库类型\")\n        cur = self.conn.cursor()\n        if not cur:\n            raise RuntimeError(\"连接数据库失败\")\n        else:\n            return cur\n\n    def query(self, sql):\n        \"\"\"执行查询语句\"\"\"\n        cur = self.connect()\n        cur.execute(sql)\n        resList = cur.fetchall()\n        self.conn.close()\n        results = []\n        for res in resList:\n            for index, value in enumerate(res):\n                if len(results) < index + 1:\n                    results.append([])\n                if isinstance(value, decimal.Decimal):\n                    if len(str(value).split(\".\")[1]) > 16:\n                        results[index].append(str(value))\n                    else:\n                        results[index].append(float(value))\n                else:\n                    results[index].append(value)\n        return results\n\n    def exec(self, sql):\n        \"\"\"执行非查询语句\"\"\"\n        cur = self.connect()\n        cur.execute(sql)\n        self.conn.commit()\n        self.conn.close()"
  },
  {
    "path": "tools/utils/utils.py",
    "content": "import json\nimport re\nfrom urllib.parse import quote\nimport jsonpath\nimport copy\n\n\ndef extract_by_jsonpath(data: (dict,list, str), expression: str):\n    if not isinstance(data, dict) and not isinstance(data, list):\n        raise ExtractValueError('被提取的值不是json, 不支持jsonpath')\n    value = jsonpath.jsonpath(data, expression)\n    if value:\n        return value[0] if len(value) == 1 else value\n    else:\n        raise ExtractValueError('jsonpath表达式错误: {}'.format(expression))\n\n\ndef extract_by_regex(data: (dict, str), pattern: str):\n    if isinstance(data, dict):\n        content = json.dumps(data, ensure_ascii=False)\n    else:\n        content = data\n    result = re.findall(pattern, content)\n    if len(result) > 0:\n        return result[0] if len(result) == 1 else result\n    else:\n        raise ExtractValueError(\"正则表达式匹配失败: {}\".format(pattern))\n\n\ndef quotation_marks(s):\n    if s[0] in [\"'\", '\"', b'\\xe2\\x80\\x98'.decode('utf-8'), b'\\xe2\\x80\\x99'.decode('utf-8'),\n                b'\\xe2\\x80\\x9c'.decode('utf-8'), b'\\xe2\\x80\\x9d'.decode('utf-8')]:\n        before = 1\n    elif s[0:2] in [\"\\\\'\", '\\\\\"']:\n        before = 2\n    else:\n        return s\n    # 后引号, 先判断转义的，在判断单个引号\n    if s[-2:] in [\"\\\\'\", '\\\\\"']:\n        after = -2\n    elif s[-1] in [\"'\", '\"', b'\\xe2\\x80\\x98'.decode('utf-8'), b'\\xe2\\x80\\x99'.decode('utf-8'),\n                   b'\\xe2\\x80\\x9c'.decode('utf-8'), b'\\xe2\\x80\\x9d'.decode('utf-8')]:\n        after = -1\n    else:\n        return s\n    return s[before:after]\n\n\ndef url_join(host: str, path: str):\n    url = \"\" if host is None or host == \"\" else (host if host.endswith('/') else host + '/')\n    api = \"\" if path is None or path == \"\" else (path[1:] if path.startswith('/') else path)\n    return url + api\n\n\ndef proxies_join(proxies: dict):\n    if 'url' not in proxies or proxies['url'] is None or len(proxies['url']) == 0:\n        raise ProxiesError(\"未设置代理网址\")\n    if not proxies['url'].startswith('http'):\n        proxies['url'] = 'http://' + proxies['url']\n    if 'username' not in proxies or proxies['username'] is None or len(proxies['username']) == 0:\n        proxies['username'] = None\n    else:\n        proxies['username'] = quote(proxies['username'], safe='')\n    if 'password' not in proxies or proxies['password'] is None or len(proxies['password']) == 0:\n        proxies['password'] = None\n    else:\n        proxies['password'] = quote(proxies['password'], safe='')\n    scheme = proxies['url'].split(':')[0]\n    if proxies['username'] is not None and proxies['password'] is not None:\n        pre, suf = proxies['url'].split('//', maxsplit=1)\n        url = '{}//{}:{}@{}'.format(pre, proxies['username'], proxies['password'], suf)\n        return {scheme: url}\n    elif proxies['username'] is None and proxies['password'] is None:\n        return {scheme: proxies['url']}\n    else:\n        raise ProxiesError(\"未设置代理账号或密码\")\n\n\ndef extract(name: str, data: (dict, list, str), expression: str):\n    if name == 'jsonpath':\n        return extract_by_jsonpath(data, expression)\n    elif name == 'regular':\n        return extract_by_regex(data, expression)\n    else:\n        raise ExtractValueError(\"未定义提取函数: {}\".format(name))\n\n\ndef get_case_message(data):\n    if isinstance(data, dict):\n        return data\n    else:\n        try:\n            return json.loads(data)\n        except json.decoder.JSONDecodeError:\n            with open(data, 'rb') as f:\n                return json.load(f)\n\n\ndef handle_operation_data(data_type, data_value):\n    try:\n        if data_type == \"JSONObject\":\n            data_value = eval(data_value)\n        elif data_type == \"JSONArray\":\n            data_value = eval(data_value)\n        elif data_type == \"Boolean\":\n            if data_value.lower() == \"true\":\n                data_value = True\n            else:\n                data_value = False\n        elif data_type == \"Int\":\n            data_value = int(data_value)\n        elif data_type == \"Float\":\n            data_value = float(data_value)\n        elif data_type == \"Number\":\n            data_value = float(data_value) if \".\" in data_value else int(data_value)\n        else:\n            data_value = data_value\n    except:\n        pass\n    return data_value\n\n\ndef handle_params_data(params):\n    result = {}\n    for key, item in params.items():\n        data_type = item[\"type\"]\n        data_value = item[\"value\"]\n        try:\n            if data_type == \"JSONObject\":\n                data_value = eval(data_value)\n            elif data_type == \"JSONArray\":\n                data_value = eval(data_value)\n            elif data_type == \"Boolean\":\n                if data_value.lower() == \"true\":\n                    data_value = True\n                else:\n                    data_value = False\n            elif data_type == \"Int\":\n                data_value = int(data_value)\n            elif data_type == \"Float\":\n                data_value = float(data_value)\n        except:\n            pass\n        result[key] = data_value\n    return result\n\n\ndef handle_form_data(form):\n    form_data = {}\n    form_file = {}\n    for item in form:\n        try:\n            if item[\"type\"] == \"File\":\n                form_file[item[\"name\"]] = \"{{@loadfile(%s)}}\" % item[\"value\"]\n            elif item[\"type\"] == \"JSONObject\":\n                form_data[item[\"name\"]] = eval(item[\"value\"])\n            elif item[\"type\"] == \"JSONArray\":\n                form_data[item[\"name\"]] = eval(item[\"value\"])\n            elif item[\"type\"] == \"Boolean\":\n                if item[\"value\"].lower() == 'true':\n                    form_data[item[\"name\"]] = True\n                else:\n                    form_data[item[\"name\"]] = False\n            elif item[\"type\"] == \"Int\":\n                form_data[item[\"name\"]] = int(item[\"value\"])\n            elif item[\"type\"] == \"Float\":\n                form_data[item[\"name\"]] = float(item[\"value\"])\n            else:\n                form_data[item[\"name\"]] = item[\"value\"]\n        except:\n            form_data[item[\"name\"]] = item[\"value\"]\n    return form_data, form_file\n\n\ndef handle_files(files):\n    body_files = []\n    for item in files:\n        file_name = item[\"name\"]\n        file_value = \"{{@loadfile(%s)}}\" % item[\"id\"]\n        body_files.append((\"file\", (file_name, file_value)))\n    return body_files\n\n\ndef json_to_path(data):\n    queue = [(\"$\", data)]\n    fina = {}\n    while len(queue) != 0:\n        (path, tar) = queue.pop()\n        if len(tar) == 0:\n            fina[\"%s\" % path] = tar\n        if isinstance(tar, dict):\n            for key, value in tar.items():\n                try:\n                    if key.isdigit():\n                        key = \"'%s'\" % str(key)\n                except:\n                    key = \"'%s'\" % str(key)\n                if isinstance(value, dict) or isinstance(value, list):\n                    queue.append((\"%s.%s\" % (path, key), value))\n                else:\n                    fina[\"%s.%s\" % (path, key)] = value\n        else:\n            for index, value in enumerate(tar):\n                if isinstance(value, dict) or isinstance(value, list):\n                    queue.append((\"%s[%d]\" % (path, index), value))\n                else:\n                    fina[\"%s[%d]\" % (path, index)] = value\n    return fina\n\n\ndef relate_sort(data, data_from):\n    not_relate_list = []\n    relate_list = []\n    for key, value in data.items():\n        if \"#{\" in str(value):\n            relate_list.append((key, value))\n        else:\n            not_relate_list.append((key, value))\n    copy_list = copy.deepcopy(relate_list)\n    sorted_list = []\n    for index in range(len(relate_list)):\n        for (key, value) in copy_list:\n            for (com_key, com_value) in copy_list:\n                if com_key[0:2] == \"$.\":\n                    json_path = com_key[2:]\n                else:\n                    json_path = com_key[1:]\n                if json_path in str(value) and com_key != key:\n                    break\n            else:\n                sorted_list.append((key, value))\n                copy_list.remove((key, value))\n                break\n    for (key, value) in sorted_list:\n        if data_from == \"query\":\n            sign = \"#{_request_query}\"\n        elif data_from == \"headers\":\n            sign = \"#{_request_header}\"\n        else:\n            sign = \"#{_request_body}\"\n        if sign in str(value).lower():\n            sorted_list.remove((key, value))\n            sorted_list.append((key, value))\n            break\n    return not_relate_list + sorted_list\n\n\ndef get_json_relation(data: dict, data_from: str):\n    return relate_sort(json_to_path(data), data_from)\n\n\nclass ExtractValueError(Exception):\n    \"\"\"提取值失败\"\"\"\n\n\nclass ProxiesError(Exception):\n    \"\"\"错误代理\"\"\"\n"
  }
]