[
  {
    "path": ".gitattributes",
    "content": "* text=auto\n*.sh text eol=lf"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Translations\n*.mo\n*.pot\n\n# Sphinx documentation\ndocs/_build/\ndocs/modules/\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Personal Files\npersonal/\nsample-data/\npyproject.toml\npoetry.lock\n\n# IDE / Editor\n.idea\n.vscode\n\ngeckodriver.log\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "version: 2\n\npython:\n   install:\n     - requirements: requirements/docs.txt\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\nsphinx:\n   configuration: docs/conf.py"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2020 Michael Herrmann\nCopyright (c) 2013 - 2019 Michael Herrmann & Tytus Dobrzynski\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "NOTICE.txt",
    "content": "LICENSES OF THIRD-PARTY LIBRARIES\n=================================\n\nHelium is based on several free and open source software libraries and\ncould not have been developed without them. Here we describle the licensing\nterms under which we are using them.\n\nSelenium Java & Python Bindings\n===============================\nURL: https://selenium.dev\nLicense: Apache License, Version 2.0\n\n Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other\n      modifications represent, as a whole, an original work of authorship.\n      For the purposes of this License, Derivative Works shall not include\n      works that remain separable from, or merely link (or bind by name) to\n      the interfaces of, the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright\n      owner or by an individual or Legal Entity authorized to submit on\n      behalf of the copyright owner. For the purposes of this definition,\n      \"submitted\" means any form of electronic, verbal, or written\n      communication sent to the Licensor or its representatives, including\n      but not limited to communication on electronic mailing lists, source\n      code control systems, and issue tracking systems that are managed by,\n      or on behalf of, the Licensor for the purpose of discussing and\n      improving the Work, but excluding communication that is conspicuously\n      marked or otherwise designated in writing by the copyright owner as\n      \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this\n      License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\nInternetExplorerDriver\n======================\nURL: https://selenium.dev\nLicense: Apache License, Version 2.0 (see above)\n\nChromeDriver\n============\nURL: https://code.google.com/p/chromedriver/\nLicense: New BSD License\n\nCopyright 2015 The Chromium Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n * Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\n   copyright notice, this list of conditions and the following disclaimer\n   in the documentation and/or other materials provided with the\n   distribution.\n * Neither the name of Google Inc. nor the names of its contributors may\n   be used to endorse or promote products derived from this software\n   without specific prior written permission.\n\n  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n  \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nApache Commons Exec\n===================\nURL: http://commons.apache.org/proper/commons-exec/\nLicense: Apache License, Version 2.0 (see above).\n\nJTwig HtmlUtils\n===============\nURL: https://github.com/lyncode/jtwig/blob/d540f2160467146a7dde0bdf9ac979687f78f194/jtwig-functions/src/main/java/com/lyncode/jtwig/functions/util/HtmlUtils.java\nVersion: d540f2160467146a7dde0bdf9ac979687f78f194\nLicense: Apache License, Version 2.0 (see above).\n\ngeckodriver\n===========\nURL: https://github.com/mozilla/geckodriver\n\nLicense:\n\nMozilla Public License Version 2.0\n----------------------------------\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in\n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0."
  },
  {
    "path": "README.md",
    "content": "# Lighter web automation with Python\n\nHelium is a Python library for automating browsers such as Chrome and Firefox.\nFor example:\n\n![Helium Demo](docs/helium-demo.gif)\n\n## Installation\n\nTo get started with Helium, you need Python 3 and Chrome or Firefox.\n\nI would recommend creating a virtual environment. This lets you install Helium\nfor just your current project, instead of globally on your whole computer.\n\nTo create and activate a virtual environment, type the following commands into\na command prompt window:\n\n```bash\npython3 -m venv venv\n# On Mac/Linux:\nsource venv/bin/activate\n# On Windows:\ncall venv\\scripts\\activate.bat\n```\nThen, you can install Helium with `pip`:\n\n```bash\npython -m pip install helium\n```\n\nNow enter `python` into the command prompt and (for instance) the commands in\nthe animation at the top of this page (`from helium import *`, ...).\n\n## Your first script\n\nI've compiled a [cheatsheet](docs/cheatsheet.md) that quickly teaches you all\nyou need to know to be productive with Helium. For a more complete reference of\nHelium's features, please see the\n[documentation](https://helium.readthedocs.io/en/latest/).\n\n## Connection to Selenium\n\nUnder the hood, Helium forwards each call to Selenium. The difference is that\nHelium's API is much more high-level. In Selenium, you need to use HTML IDs,\nXPaths and CSS selectors to identify web page elements. Helium on the other hand\nlets you refer to elements by user-visible labels. As a result, Helium scripts\nare typically 30-50% shorter than similar Selenium scripts. What's more, they\nare easier to read and more stable with respect to changes in the underlying web\npage.\n\nBecause Helium is simply a wrapper around Selenium, you can freely mix the two\nlibraries. For example:\n\n```python\n# A Helium function:\ndriver = start_chrome()\n# A Selenium API:\ndriver.execute_script(\"alert('Hi!');\")\n```\n\nSo in other words, you don't lose anything by using Helium over pure Selenium.\n\nIn addition to its more high-level API, Helium simplifies further tasks that are\ntraditionally painful in Selenium:\n\n- **iFrames:** Unlike Selenium, Helium lets you interact with elements inside\n  nested iFrames, without having to first \"switch to\" the iFrame.\n- **Window management.** Helium notices when popups open or close and focuses /\n  defocuses them like a user would. You can also easily switch to a window by\n  (parts of) its title. No more having to iterate over Selenium window handles.\n- **Implicit waits.** By default, if you try click on an element with Selenium\n  and that element is not yet present on the page, your script fails. Helium by\n  default waits up to 10 seconds for the element to appear.\n- **Explicit waits.** Helium gives you a much nicer API for waiting for a\n  condition on the web page to become true. For example: To wait for an element\n  to appear in Selenium, you would write:\n  ```python\n  element = WebDriverWait(driver, 10).until(\n      EC.presence_of_element_located((By.ID, \"myDynamicElement\"))\n  )\n  ```\n  With Helium, you can write:\n  ```python\n  wait_until(Button('Download').exists)\n  ```\n\n## Status of this project\n\nI have too little spare time to maintain this project for free. If you'd like\nmy help, please go to my [web site](http://herrmann.io) to ask about my\nconsulting rates. Otherwise, unless it is very easy for me, I will usually not\nrespond to emails or issues on the issue tracker. I will however accept and\nmerge PRs. So if you add some functionality to Helium that may be useful for\nothers, do share it with us by creating a Pull Request. For instructions, please\nsee [Contributing](#Contributing) below.\n\n## How you can help\n\nI find Helium extremely useful in my own projects and feel it should be more\nwidely known. Here's how you can help with this:\n\n- Star this project on GitHub.\n- Tell your friends and colleagues about it.\n- [Share it on Twitter with one click](https://twitter.com/intent/tweet?text=I%20find%20Helium%20very%20useful%20for%20web%20automation%20with%20Python%3A%20https%3A//github.com/mherrmann/helium)\n- Share it on other social media\n- Write a blog post about Helium.\n\nWith this, I think we can eventually make Helium the de-facto standard for web\nautomation in Python.\n\n## Contributing\n\nPull Requests are very welcome. Please follow the same coding conventions as the\nrest of the code, in particular the use of tabs over spaces. Also, read through my\n[PR guidelines](https://gist.github.com/mherrmann/5ce21814789152c17abd91c0b3eaadca).\nDoing this will save you (and me) unnecessary effort.\n\nBefore you submit a PR, ensure that the tests still work:\n\n```bash\npip install -Ur requirements/test.txt\npython setup.py test\n```\n\nThis runs the tests against Chrome. To run them against Firefox, set the\nenvironment variable `TEST_BROWSER` to `firefox`. Eg. on Mac/Linux:\n\n```bash\nTEST_BROWSER=firefox python setup.py test\n```\n\nOn Windows:\n\n```bash\nset TEST_BROWSER=firefox\npython setup.py test\n```\n\nIf you do add new functionality, you should also add tests for it. Please see\nthe [`tests/`](tests) directory for what this might look like.\n\n## History\n\nI (Michael Herrmann) originally developed Helium in 2013 for a Polish IT startup\ncalled BugFree software. (It could be that you have seen Helium before at\nhttps://heliumhq.com.) We shut down the company at the end of 2019 and I felt it\nwould be a shame if Helium simply disappeared from the face of the earth. So I\ninvested some time to modernize it and bring it into a state suitable for open\nsource.\n\nHelium used to be available for both Java and Python. But because I now only\nuse it from Python, I didn't have time to bring the Java implementation up to\nspeed as well. Similarly for Internet Explorer: Helium used to support it, but\nsince I have no need for it, I removed the (probably broken) old implementation.\n\nThe name Helium was chosen because it is also a chemical element like Selenium,\nbut it is lighter.\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Welcome to Helium's documentation\n\nThe documentation is built using\n[sphinx](https://www.sphinx-doc.org/en/master/index.html) and the theme used is\n[sphinx-rtd-theme](https://sphinx-rtd-theme.readthedocs.io/en/stable/).\n\n## Setting up documentation locally\n\nEnsure you have `python` and `pip` installed on your\nsystem and then run this command in the project root:\n\n```bash\npip install -Ur requirements/docs.txt\nmake -C docs/ html\n```\n\nThis will install all development dependencies for the project and then build\nthe documentation in HTML format in `docs/_build/` directory. Open\n`docs/_build/index.html` in your browser to see the documentation.\n"
  },
  {
    "path": "docs/api.rst",
    "content": "API\n===\n\n.. automodule:: helium\n    :members:\n"
  },
  {
    "path": "docs/cheatsheet.md",
    "content": "# Helium cheatsheet\n\nThis page very quickly teaches you the most important parts of Helium's API.\n\n## Importing\n\nAll of Helium's public functions lie directly in the module `helium`.\nYou can for instance import them as follows:\n\n```python\nfrom helium import *\n```\n\n## Starting a browser\n\nHelium currently supports Chrome and Firefox. You can start them with the\nfollowing functions:\n\n```python\nstart_chrome()\nstart_firefox()\n```\n\nYou can optionally pass a URL to open (eg. `start_chrome('google.com')`)\n\n## Headless browser\n\nWhen you type the above commands, you will actually see a browser window open.\nThis is useful for developing your scripts. However, once you run them, you may\nnot want this window to appear. You can achieve this by adding `headless=True`:\n\n```python\nstart_chrome(headless=True)\nstart_chrome('google.com', headless=True)\n```\n\n(Similarly for `start_firefox(...)` of course.)\n\n## Interacting with a web site\n\nThe following example shows the most typical statements in a Helium script:\n\n```python\nfrom helium import *\nstart_chrome('google.com')\nwrite('helium selenium github')\npress(ENTER)\nclick('mherrmann/helium')\ngo_to('github.com/login')\nwrite('username', into='Username')\nwrite('password', into='Password')\nclick('Sign in')\nkill_browser()\n```\n\nMost of your own code will (hopefully) be as simple as the above.\n\n## Element types\n\nThe above example used pure strings such as `Sign in` to identify elements on\nthe web page. But Helium also lets you target elements more specifically.\nFor instance:\n\n * [`Link('Sign in')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L643)\n * [`Button('Sign in')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L706)\n * [`TextField('First name')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L768)\n * [`CheckBox('I accept')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L867)\n * [`RadioButton('Windows')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L907)\n * [`Image(alt='Helium logo')`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L739)\n\nYou can pass them into other functions such as `click(Link('Sign in'))`.\nBut you can also use them to _read_ data from the web site. For instance:\n\n```python\nprint(TextField('First name').value)\n```\n\nA common use case is to use `.exists()` to check for the existence of an\nelement. For example:\n\n```python\nif Text('Accept cookies?').exists():\n    click('I accept')\n```\n\nI also often find `Text(...).value` useful for reading out data:\n\n```python\nname = Text(to_right_of='Name:', below=Image(alt='Profile picture')).value\n```\n\nFor a full list of element types and their properties, please see\n[the source code](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L470-L1008).\n\n## Finding elements relative to others\n\nYou already saw in the previous section how `above=...` and `to_right_of=...`\nlet you find elements relative to other elements. You can similarly use\n`below=...` and `to_left_of`. Here are some more examples.\n\n```python\nText(above='Balance', below='Transactions').value\nLink(to_right_of='Invoice:')\nImage(to_right_of=Link('Sign in', below=Text('Navigation')))\n```\n\n## Waiting for elements to appear (or other conditions)\n\nUse\n[`wait_until(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L410)\nto wait for a condition to become true. For example:\n\n```python\nwait_until(Button('Download').exists)\n```\n\nBut you can also use this to wait for an arbitrary condition:\n\n```python\nwait_until(lambda: TextField('Balance').value == '$2M')\n```\n\n## jQuery-style selectors\n\nSometimes, you do need to fall back to using HTML IDs, CSS Selectors or XPaths\nto identify an element on the web page. Helium's\n[`S(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L568)\npredicate lets you do this. The parameter you pass to it is interpreted as\nfollows:\n\n* If it starts with an ``@``, then it identifies elements by HTML ``name``.\n  Eg. ``S(\"@btnName\")`` identifies an element with ``name=\"btnName\"``.\n* If it starts with ``//``, then Helium interprets it as an XPath.\n* Otherwise, Helium interprets it as a CSS selector. This in particular\n  lets you write ``S(\"#myId\")`` to identify an element with ``id=\"myId\"``,\n  or ``S(\".myClass\")`` to identify elements with ``class=\"myClass\"``.\n\nAs before, you can combine `S(...)` with other functions such as\n`click(S(...))`, or use it to extract data. For an example of this, see\n[below](#finding-all-elements).\n\n## Combining Helium and Selenium's APIs\n\nAll Helium does is translate your high-level commands into low-level Selenium\nfunction calls. Because of this, you can freely mix Selenium and Helium. For\nexample:\n\n```python\n# A Helium function:\ndriver = start_chrome()\n# A Selenium API:\ndriver.execute_script(\"alert('Hi!');\")\n```\n\nYou can also get / set the Selenium WebDriver which Helium uses via\n[`get_driver()`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L104)\nand\n[`set_driver(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L97).\n\nWith the WebDriver instance, you can execute any Selenium commands you want.\n\nTo use Helium's API's to obtain Selenium `WebElement`s, use the `.web_element`\nproperty of Helium's various GUI elements. For instance:\n\n```python\n# Get the CSS class of the \"Helium\" link:\nLink('Helium').web_element.get_attribute('class')\n```\n\nHere, `.get_attribute(...)` is a Selenium API.\n\n\n## Finding all elements\n\nThe `.web_element` property and the `S(...)` predicate are particularly useful\nfor extracting multiple pieces of data from a web page. To do this, you can use\nHelium's [`find_all(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L281) function.\nAs its name implies, it lets you find all occurrences of an element on a page.\nFor example:\n\n```python\nemail_cells = find_all(S(\"table > tr > td\", below=\"Email\"))\nemails = [cell.web_element.text for cell in email_cells]\n```\n\n## Implicit waits\n\nWhen you issue a command such as `click('Download')`, Helium by default waits\nup to 10 seconds for the respective element to appear. This feature is called\n\"implicit waiting\". You can change the 10 second default to a different value\nvia the\n[`Config` class](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L437):\n\n```python\nConfig.implicit_wait_secs = 30\n```\n\nHowever, before you do this, it may be better to add explicit waits to your\ncode, such as `wait_until(Button('Download').exists)`.\n\n## Alerts\n\nThe\n[`Alert` class](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L970)\nlets you interface with JavaScript popup boxes. Use `Alert().accept()`,\n`Alert().dismiss()` to click \"Ok\" or \"Cancel\", `Alert().text` to read the\nmessage shown, or `write(..., into=Alert())` to enter a value.\n\n## File uploads, drag and drop, combo boxes, popups\n\nUse\n[`attach_file(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L388),\n[`drag_file(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L375),\n[`drag(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L252),\n[`select(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L362),\n[`switch_to(...)`](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L1057).\n\n## Clicking at x, y coordinates\n\nSometimes, you may want to click at a specific `(x, y)` coordinate, or at an\noffset of an element. \n\n### Create a `Point` to specify the coordinates and use it to click:\n\n```python\nfrom helium import click\npoint = Point(x=100, y=200)\nclick(point)  # Clicks at (100, 200)\n```\n\n### Adjusting a Point by a offset\nYou can modify a point's position using addition or subtraction by a delta:\n\n```python\ndelta = (20, -10)\nclick(Point(100, 200) + delta)  # Clicks at (120, 190)\n```\n\nSee the\n[`Point` class](https://github.com/mherrmann/helium/blob/0667ddb9be531367a0d707ad8f5fcfb75c528521/helium/__init__.py#L1010)\nfor more.\n\n## Taking a screenshot\n\nUse Selenium's API:\n\n```python\nget_driver().save_screenshot(r'C:\\screenshot.png')\n```\n\nNote the leading `r`. This is required because the string contains a backslash\n`\\`.\n"
  },
  {
    "path": "docs/conf.py",
    "content": "import os\nimport sys\nfrom datetime import date\n\nsys.path.insert(0, os.path.abspath('..'))\n\n\n# -- Project information -----------------------------------------------------\n\nproject = 'helium'\ncopyright = '%s, Michael Herrmann' % date.today().year\nauthor = 'Michael Herrmann'\n\n# Also update ../setup.py when you change this:\nrelease = '7.0.0'\n\n\n# -- General configuration ---------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx.ext.githubpages', 'sphinx_rtd_theme']\n\nhtml_theme = \"sphinx_rtd_theme\"\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\nautodoc_member_order = 'bysource'"
  },
  {
    "path": "docs/contributors.rst",
    "content": "Contributors to this project\n============================\n..\n    Please use this format to add your contributions to this file\n    `SocialUsernameName <Profile-Url>`_ (**Your Name**) - *Description of your contribution in a few words*\n\n- `mherrmann <https://github.com/mherrmann>`_ (**Michael Herrmann**) - *Project creator and maintainer*\n- `IgnisDa <https://github.com/IgnisDa>`_ (**Diptesh Choudhuri**) - *Documentation maintainer*\n\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Welcome to Helium's documentation!\n==================================\n\nHelium is a Python library for automating web sites. It is based on\n`Selenium-python <https://selenium-python.readthedocs.io/>`_.\nSelenium is great, but difficult to use. Helium wraps around Selenium to give\nyou a simpler API. Helium's name comes from being a lighter chemical element\nthan Selenium.\n\nFor a quick overview of Helium's features, please see\n`the project home page <https://github.com/mherrmann/helium>`_.\nHere, in the documentation, you will find a more comprehensive reference.\n\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Contents:\n\n   installation.rst\n   api.rst\n   contributors.rst\n\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/installation.rst",
    "content": "Installation\n============\n\nTo install Helium, you need Python 3 and Chrome or Firefox.\n\nIf you already know Python, then the following command should be all you need:\n\n\n.. code-block:: bash\n\n    pip install helium\n\nOtherwise - Hi! I would recommend you create a virtual environment in the\ncurrent directory. Any libraries you download (such as Helium) will be placed\nthere. Enter the following into a command prompt:\n\n.. code-block:: bash\n\n    python3 -m venv venv\n\nThis creates a virtual environment in the `venv/` directory. To activate it:\n\n.. code-block:: bash\n\n    # On Mac/Linux, bash shell:\n    source venv/bin/activate\n    # On Windows:\n    call venv\\Scripts\\activate.bat\n\nThen, install Helium using `pip`:\n\n.. code-block:: bash\n\n    python -m pip install helium\n\nNow enter :code:`python` into the command prompt and the command :code:`from\nhelium import *` and you are ready to get started!\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset SOURCEDIR=.\nset BUILDDIR=_build\n\nif \"%1\" == \"\" goto help\n\n%SPHINXBUILD% >NUL 2>NUL\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\ngoto end\n\n:help\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\n\n:end\npopd\n"
  },
  {
    "path": "helium/__init__.py",
    "content": "\"\"\"\nHelium's API is contained in module ``helium``. It is a simple Python API that\nmakes specifying web automation cases as simple as describing them to someone\nlooking over their shoulder at a screen.\n\nThe public functions and classes of Helium are listed below. If you wish to use\nHelium functions in your Python scripts you can import them from the\n``helium`` module::\n\n\tfrom helium import *\n\"\"\"\nfrom collections import namedtuple, OrderedDict\nfrom copy import copy\nfrom helium._impl import APIImpl\nfrom helium._impl.util.html import get_easily_readable_snippet\nfrom helium._impl.util.inspect_ import repr_args\nfrom selenium.common import NoSuchElementException\nfrom selenium.webdriver.common.keys import Keys\n\nimport helium._impl\n\ndef start_chrome(url=None, headless=False, maximize=False, options=None):\n\t\"\"\"\n\t:param url: URL to open.\n\t:type url: str\n\t:param headless: Whether to start Chrome in headless mode.\n\t:type headless: bool\n\t:param maximize: Whether to maximize the Chrome window.\n\t                 Ignored when `headless` is set to `True`.\n\t:type maximize: bool\n\t:param options: ChromeOptions to use for starting the browser\n\t:type options: :py:class:`selenium.webdriver.ChromeOptions`\n\n\tStarts an instance of Google Chrome::\n\n\t\tstart_chrome()\n\n\tYou can optionally open a URL::\n\n\t\tstart_chrome(\"google.com\")\n\n\tThe `headless` switch lets you prevent the browser window from appearing on\n\tyour screen::\n\n\t\tstart_chrome(headless=True)\n\t\tstart_chrome(\"google.com\", headless=True)\n\n\tFor more advanced configuration, use the `options` parameter::\n\n\t\tfrom selenium.webdriver import ChromeOptions\n\t\toptions = ChromeOptions()\n\t\toptions.add_argument('--proxy-server=1.2.3.4:5678')\n\t\toptions.set_capability('goog:loggingPrefs', {'performance': 'ALL'})\n\t\tstart_chrome(options=options)\n\n\tWhen no compatible ChromeDriver is found on your `PATH`, then `start_chrome`\n\tautomatically downloads it using Selenium Manager.\n\n\tOn shutdown of the Python interpreter, Helium terminates the ChromeDriver\n\tprocess but does not close the browser itself. If you want to close the\n\tbrowser at the end of your script, use the following command::\n\n\t\tkill_browser()\n\t\"\"\"\n\treturn _get_api_impl().start_chrome_impl(url, headless, maximize, options)\n\ndef start_firefox(url=None, headless=False, options=None, profile=None):\n\t\"\"\"\n\t:param url: URL to open.\n\t:type url: str\n\t:param headless: Whether to start Firefox in headless mode.\n\t:type headless: bool\n\t:param options: FirefoxOptions to use for starting the browser.\n\t:type options: :py:class:`selenium.webdriver.FirefoxOptions`\n\t:param profile: FirefoxProfile to use for starting the browser.\n\t:type profile: :py:class:`selenium.webdriver.FirefoxProfile`\n\n\tStarts an instance of Firefox::\n\n\t\tstart_firefox()\n\n\tIf this doesn't work for you, then it may be that Helium's copy of\n\tgeckodriver is not compatible with your version of Firefox. To fix this,\n\tplace a copy of geckodriver on your `PATH`.\n\n\tYou can optionally open a URL::\n\n\t\tstart_firefox(\"google.com\")\n\n\tThe `headless` switch lets you prevent the browser window from appearing on\n\tyour screen::\n\n\t\tstart_firefox(headless=True)\n\t\tstart_firefox(\"google.com\", headless=True)\n\n\tFor more advanced configuration, use the `options` parameter::\n\n\t\tfrom selenium.webdriver import FirefoxOptions\n\t\toptions = FirefoxOptions()\n\t\toptions.add_argument(\"--width=2560\")\n\t\toptions.add_argument(\"--height=1440\")\n\t\tstart_firefox(options=options)\n\n\tTo set proxy, useragent, etc. (ie. things you find in about:config), use the\n\t`profile` parameter::\n\n\t\tfrom selenium.webdriver import FirefoxProfile\n\t\tprofile = FirefoxProfile()\n\t\tSOCKS5_PROXY_HOST = \"0.0.0.0\"\n\t\tPROXY_PORT = 0\n\t\tprofile.set_preference(\"network.proxy.type\", 1)\n\t\tprofile.set_preference(\"network.proxy.socks\", SOCKS5_PROXY_HOST)\n\t\tprofile.set_preference(\"network.proxy.socks_port\", PROXY_PORT)\n\t\tprofile.set_preference(\"network.proxy.socks_remote_dns\", True)\n\t\tprofile.set_preference(\"network.proxy.socks_version\", 5)\n\t\tprofile.set_preference(\"network.proxy.no_proxies_on\", \"localhost,\n\t\t                       10.20.30.40\")\n\t\tUSER_AGENT = \"Mozilla/5.0 ...\"\n\t\tprofile.set_preference(\"general.useragent.override\", USER_AGENT)\n\t\tstart_firefox(profile=profile)\n\n\tOn shutdown of the Python interpreter, Helium cleans up all resources used\n\tfor controlling the browser (such as the geckodriver process), but does\n\tnot close the browser itself. If you want to terminate the browser at the\n\tend of your script, use the following command::\n\n\t\tkill_browser()\n\t\"\"\"\n\treturn _get_api_impl().start_firefox_impl(url, headless, options, profile)\n\ndef go_to(url):\n\t\"\"\"\n\t:param url: URL to open.\n\t:type url: str\n\n\tOpens the specified URL in the current web browser window. For instance::\n\n\t\tgo_to(\"google.com\")\n\t\"\"\"\n\t_get_api_impl().go_to_impl(url)\n\ndef set_driver(driver):\n\t\"\"\"\n\tSets the Selenium WebDriver used to execute Helium commands. See also\n\t:py:func:`get_driver`.\n\t\"\"\"\n\t_get_api_impl().set_driver_impl(driver)\n\ndef get_driver():\n\t\"\"\"\n\tReturns the Selenium WebDriver currently used by Helium to execute all\n\tcommands. Each Helium command such as ``click(\"Login\")`` is translated to a\n\tsequence of Selenium commands that are issued to this driver.\n\t\"\"\"\n\treturn _get_api_impl().get_driver_impl()\n\ndef write(text, into=None):\n\t\"\"\"\n\t:param text: The text to be written.\n\t:type text: one of str, unicode\n\t:param into: The element to write into.\n\t:type into: one of str, unicode, :py:class:`HTMLElement`, \\\n:py:class:`selenium.webdriver.remote.webelement.WebElement`, :py:class:`Alert`\n\n\tTypes the given text into the active window. If parameter 'into' is given,\n\twrites the text into the text field or element identified by that parameter.\n\tCommon examples of 'write' are::\n\n\t\twrite(\"Hello World!\")\n\t\twrite(\"user12345\", into=\"Username:\")\n\t\twrite(\"Michael\", into=Alert(\"Please enter your name\"))\n\t\"\"\"\n\t_get_api_impl().write_impl(text, into)\n\ndef press(key):\n\t\"\"\"\n\t:param key: Key or combination of keys to be pressed.\n\n\tPresses the given key or key combination. To press a normal letter key such\n\tas 'a' simply call `press` for it::\n\n\t\tpress('a')\n\n\tYou can also simulate the pressing of upper case characters that way::\n\n\t\tpress('A')\n\n\tThe special keys you can press are those given by Selenium's class\n\t:py:class:`selenium.webdriver.common.keys.Keys`. Helium makes all those keys\n\tavailable through its namespace, so you can just use them without having to\n\trefer to :py:class:`selenium.webdriver.common.keys.Keys`. For instance, to\n\tpress the Enter key::\n\n\t\tpress(ENTER)\n\n\tTo press multiple keys at the same time, concatenate them with `+`. For\n\texample, to press Control + a, call::\n\n\t\tpress(CONTROL + 'a')\n\t\"\"\"\n\t_get_api_impl().press_impl(key)\n\nNULL         = Keys.NULL\nCANCEL       = Keys.CANCEL\nHELP         = Keys.HELP\nBACK_SPACE   = Keys.BACK_SPACE\nTAB          = Keys.TAB\nCLEAR        = Keys.CLEAR\nRETURN       = Keys.RETURN\nENTER        = Keys.ENTER\nSHIFT        = Keys.SHIFT\nLEFT_SHIFT   = Keys.LEFT_SHIFT\nCONTROL      = Keys.CONTROL\nLEFT_CONTROL = Keys.LEFT_CONTROL\nALT          = Keys.ALT\nLEFT_ALT     = Keys.LEFT_ALT\nPAUSE        = Keys.PAUSE\nESCAPE       = Keys.ESCAPE\nSPACE        = Keys.SPACE\nPAGE_UP      = Keys.PAGE_UP\nPAGE_DOWN    = Keys.PAGE_DOWN\nEND          = Keys.END\nHOME         = Keys.HOME\nLEFT         = Keys.LEFT\nARROW_LEFT   = Keys.ARROW_LEFT\nUP           = Keys.UP\nARROW_UP     = Keys.ARROW_UP\nRIGHT        = Keys.RIGHT\nARROW_RIGHT  = Keys.ARROW_RIGHT\nDOWN         = Keys.DOWN\nARROW_DOWN   = Keys.ARROW_DOWN\nINSERT       = Keys.INSERT\nDELETE       = Keys.DELETE\nSEMICOLON    = Keys.SEMICOLON\nEQUALS       = Keys.EQUALS\nNUMPAD0      = Keys.NUMPAD0\nNUMPAD1      = Keys.NUMPAD1\nNUMPAD2      = Keys.NUMPAD2\nNUMPAD3      = Keys.NUMPAD3\nNUMPAD4      = Keys.NUMPAD4\nNUMPAD5      = Keys.NUMPAD5\nNUMPAD6      = Keys.NUMPAD6\nNUMPAD7      = Keys.NUMPAD7\nNUMPAD8      = Keys.NUMPAD8\nNUMPAD9      = Keys.NUMPAD9\nMULTIPLY     = Keys.MULTIPLY\nADD          = Keys.ADD\nSEPARATOR    = Keys.SEPARATOR\nSUBTRACT     = Keys.SUBTRACT\nDECIMAL      = Keys.DECIMAL\nDIVIDE       = Keys.DIVIDE\nF1           = Keys.F1\nF2           = Keys.F2\nF3           = Keys.F3\nF4           = Keys.F4\nF5           = Keys.F5\nF6           = Keys.F6\nF7           = Keys.F7\nF8           = Keys.F8\nF9           = Keys.F9\nF10          = Keys.F10\nF11          = Keys.F11\nF12          = Keys.F12\nMETA         = Keys.META\nCOMMAND      = Keys.COMMAND\n\ndef click(element):\n\t\"\"\"\n\t:param element: The element or point to click.\n\t:type element: str, unicode, :py:class:`HTMLElement`, \\\n:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`\n\n\tClicks on the given element or point. Common examples are::\n\n\t\tclick(\"Sign in\")\n\t\tclick(Button(\"OK\"))\n\t\tclick(Point(200, 300))\n\t\tclick(ComboBox(\"File type\").top_left + (50, 0))\n\t\"\"\"\n\t_get_api_impl().click_impl(element)\n\ndef doubleclick(element):\n\t\"\"\"\n\t:param element: The element or point to click.\n\t:type element: str, unicode, :py:class:`HTMLElement`, \\\n:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`\n\n\tPerforms a double-click on the given element or point. For example::\n\n\t\tdoubleclick(\"Double click here\")\n\t\tdoubleclick(Image(\"Directories\"))\n\t\tdoubleclick(Point(200, 300))\n\t\tdoubleclick(TextField(\"Username\").top_left - (0, 20))\n\t\"\"\"\n\t_get_api_impl().doubleclick_impl(element)\n\ndef drag(element, to):\n\t\"\"\"\n\t:param element: The element or point to drag.\n\t:type element: str, unicode, :py:class:`HTMLElement`, \\\n:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`\n\t:param to: The element or point to drag to.\n\t:type to: str, unicode, :py:class:`HTMLElement`, \\\n:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`\n\n\tDrags the given element or point to the given location. For example::\n\n\t\tdrag(\"Drag me!\", to=\"Drop here.\")\n\n\tThe dragging is performed by hovering the mouse cursor over ``element``,\n\tpressing and holding the left mouse button, moving the mouse cursor over\n\t``to``, and then releasing the left mouse button again.\n\n\tThis function is exclusively used for dragging elements inside one web page.\n\tIf you wish to drag a file from the hard disk onto the browser window (eg.\n\tto initiate a file upload), use function :py:func:`drag_file`.\n\t\"\"\"\n\t_get_api_impl().drag_impl(element, to)\n\ndef press_mouse_on(element):\n\t_get_api_impl().press_mouse_on_impl(element)\n\ndef release_mouse_over(element):\n\t_get_api_impl().release_mouse_over_impl(element)\n\ndef find_all(predicate):\n\t\"\"\"\n\tLets you find all occurrences of the given GUI element predicate. For\n\tinstance, the following statement returns a list of all buttons with label\n\t\"Open\"::\n\n\t\tfind_all(Button(\"Open\"))\n\n\tOther examples are::\n\n\t\tfind_all(Window())\n\t\tfind_all(TextField(\"Address line 1\"))\n\n\tThe function returns a list of elements of the same type as the passed-in\n\tparameter. For instance, ``find_all(Button(...))`` yields a list whose\n\telements are of type :py:class:`Button`.\n\n\tIn a typical usage scenario, you want to pick out one of the occurrences\n\treturned by :py:func:`find_all`. In such cases, :py:func:`list.sort` can\n\tbe very useful. For example, to find the leftmost \"Open\" button, you can\n\twrite::\n\n\t\tbuttons = find_all(Button(\"Open\"))\n\t\tleftmost_button = sorted(buttons, key=lambda button: button.x)[0]\n\t\"\"\"\n\treturn _get_api_impl().find_all_impl(predicate)\n\ndef scroll_down(num_pixels=100):\n\t\"\"\"\n\tScrolls down the page the given number of pixels.\n\t\"\"\"\n\t_get_api_impl().scroll_down_impl(num_pixels)\n\ndef scroll_up(num_pixels=100):\n\t\"\"\"\n\tScrolls the the page up the given number of pixels.\n\t\"\"\"\n\t_get_api_impl().scroll_up_impl(num_pixels)\n\ndef scroll_right(num_pixels=100):\n\t\"\"\"\n\tScrolls the page to the right the given number of pixels.\n\t\"\"\"\n\t_get_api_impl().scroll_right_impl(num_pixels)\n\ndef scroll_left(num_pixels=100):\n\t\"\"\"\n\tScrolls the page to the left the given number of pixels.\n\t\"\"\"\n\t_get_api_impl().scroll_left_impl(num_pixels)\n\ndef hover(element):\n\t\"\"\"\n\t:param element: The element or point to hover.\n\t:type element: str, unicode, :py:class:`HTMLElement`, \\\n:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`\n\n\tHovers the mouse cursor over the given element or point. For example::\n\n\t\thover(\"File size\")\n\t\thover(Button(\"OK\"))\n\t\thover(Link(\"Download\"))\n\t\thover(Point(200, 300))\n\t\thover(ComboBox(\"File type\").top_left + (50, 0))\n\t\"\"\"\n\t_get_api_impl().hover_impl(element)\n\ndef rightclick(element):\n\t\"\"\"\n\t:param element: The element or point to click.\n\t:type element: str, unicode, :py:class:`HTMLElement`, \\\n:py:class:`selenium.webdriver.remote.webelement.WebElement` or :py:class:`Point`\n\n\tPerforms a right click on the given element or point. For example::\n\n\t\trightclick(\"Something\")\n\t\trightclick(Point(200, 300))\n\t\trightclick(Image(\"captcha\"))\n\t\"\"\"\n\t_get_api_impl().rightclick_impl(element)\n\ndef select(combo_box, value):\n\t\"\"\"\n\t:param combo_box: The combo box whose value should be changed.\n\t:type combo_box: str, unicode or :py:class:`ComboBox`\n\t:param value: The visible value of the combo box to be selected.\n\n\tSelects a value from a combo box. For example::\n\n\t\tselect(\"Language\", \"English\")\n\t\tselect(ComboBox(\"Language\"), \"English\")\n\t\"\"\"\n\t_get_api_impl().select_impl(combo_box, value)\n\ndef drag_file(file_path, to):\n\t\"\"\"\n\tSimulates the dragging of a file from the computer over the browser window\n\tand dropping it over the given element. This allows, for example, to attach\n\tfiles to emails in Gmail::\n\n\t\tclick(\"COMPOSE\")\n\t\twrite(\"example@gmail.com\", into=\"To\")\n\t\twrite(\"Email subject\", into=\"Subject\")\n\t\tdrag_file(r\"C:\\\\Documents\\\\notes.txt\", to=\"Drop files here\")\n\t\"\"\"\n\t_get_api_impl().drag_file_impl(file_path, to)\n\ndef attach_file(file_path, to=None):\n\t\"\"\"\n\t:param file_path: The path of the file to be attached.\n\t:param to: The file input element to which the file should be attached.\n\n\tAllows attaching a file to a file input element. For instance::\n\n\t\tattach_file(\"c:/test.txt\", to=\"Please select a file:\")\n\n\tThe file input element is identified by its label. If you omit the ``to=``\n\tparameter, then Helium attaches the file to the first file input element it\n\tfinds on the page.\n\t\"\"\"\n\t_get_api_impl().attach_file_impl(file_path, to=to)\n\ndef refresh():\n\t\"\"\"\n\tRefreshes the current page. If an alert dialog is open, then Helium first\n\tcloses it.\n\t\"\"\"\n\t_get_api_impl().refresh_impl()\n\ndef wait_until(condition_fn, timeout_secs=10, interval_secs=0.5):\n\t\"\"\"\n\t:param condition_fn: A function taking no arguments that represents the \\\n\tcondition to be waited for.\n\t:param timeout_secs: The timeout, in seconds, after which the condition is \\\n\tdeemed to have failed.\n\t:param interval_secs: The interval, in seconds, at which the condition \\\n\tfunction is polled to determine whether the wait has succeeded.\n\n\tWaits until the given condition function evaluates to true. This is most\n\tcommonly used to wait for an element to exist::\n\n\t\twait_until(Text(\"Finished!\").exists)\n\n\tMore elaborate conditions are also possible using Python lambda\n\texpressions. For instance, to wait until a text no longer exists::\n\n\t\twait_until(lambda: not Text(\"Uploading...\").exists())\n\n\t``wait_until`` raises\n\t:py:class:`selenium.common.exceptions.TimeoutException` if the condition is\n\tnot satisfied within the given number of seconds. The parameter\n\t``interval_secs`` specifies the number of seconds Helium waits between\n\tevaluating the condition function.\n\t\"\"\"\n\t_get_api_impl().wait_until_impl(condition_fn, timeout_secs, interval_secs)\n\nclass Config:\n\t\"\"\"\n\tThis class contains Helium's run-time configuration. To modify Helium's\n\tbehaviour, simply assign to the properties of this class. For instance::\n\n\t\tConfig.implicit_wait_secs = 0\n\t\"\"\"\n\timplicit_wait_secs = 10\n\t\"\"\"\n\t``implicit_wait_secs`` is Helium's analogue to Selenium's\n\t``.implicitly_wait(secs)``. Suppose you have a script that executes the\n\tfollowing command::\n\n\t\t>>> click(\"Download\")\n\n\tIf the \"Download\" element is not immediately available, then Helium waits up\n\tto ``implicit_wait_secs`` for it to appear before raising a\n\t``LookupError``. This is useful in situations where the page takes slightly\n\tlonger to load, or a GUI element only appears after a certain time.\n\n\tTo disable Helium's implicit waits, simply execute::\n\n\t\tConfig.implicit_wait_secs = 0\n\n\tHelium's implicit waits do not affect commands :py:func:`find_all` or\n\t:py:func:`GUIElement.exists`. Note also that setting\n\t``implicit_wait_secs`` does not affect the underlying Selenium driver\n\t(see :py:func:`get_driver`).\n\n\tFor the best results, it is recommended to not use Selenium's\n\t``.implicitly_wait(...)`` in conjunction with Helium.\n\t\"\"\"\n\nclass GUIElement:\n\tdef __init__(self):\n\t\tself._driver = _get_api_impl().require_driver()\n\t\tself._args = []\n\t\tself._kwargs = OrderedDict()\n\t\tself._impl_cached = None\n\tdef exists(self):\n\t\t\"\"\"\n\t\tEvaluates to true if this GUI element exists.\n\t\t\"\"\"\n\t\treturn self._impl.exists()\n\tdef with_impl(self, impl):\n\t\tresult = copy(self)\n\t\tresult._impl = impl\n\t\treturn result\n\t@property\n\tdef _impl(self):\n\t\tif self._impl_cached is None:\n\t\t\timpl_class = \\\n\t\t\t\tgetattr(helium._impl, self.__class__.__name__ + 'Impl')\n\t\t\tself._impl_cached = impl_class(\n\t\t\t\tself._driver, *self._args, **self._kwargs\n\t\t\t)\n\t\treturn self._impl_cached\n\t@_impl.setter\n\tdef _impl(self, value):\n\t\tself._impl_cached = value\n\tdef __repr__(self):\n\t\treturn self._repr_constructor_args(self._args, self._kwargs)\n\tdef _repr_constructor_args(self, args=None, kwargs=None):\n\t\tif args is None:\n\t\t\targs = []\n\t\tif kwargs is None:\n\t\t\tkwargs = {}\n\t\treturn '%s(%s)' % (\n\t\t\tself.__class__.__name__,\n\t\t\trepr_args(self.__init__, args, kwargs, repr)\n\t\t)\n\tdef _is_bound(self):\n\t\treturn self._impl_cached is not None and self._impl_cached._is_bound()\n\nclass HTMLElement(GUIElement):\n\tdef __init__(\n\t\t\tself, below=None, to_right_of=None, above=None, to_left_of=None\n\t):\n\t\tsuper(HTMLElement, self).__init__()\n\t\tself._kwargs['below'] = below\n\t\tself._kwargs['to_right_of'] = to_right_of\n\t\tself._kwargs['above'] = above\n\t\tself._kwargs['to_left_of'] = to_left_of\n\t@property\n\tdef width(self):\n\t\t\"\"\"\n\t\tThe width of this HTML element, in pixels.\n\t\t\"\"\"\n\t\treturn self._impl.width\n\t@property\n\tdef height(self):\n\t\t\"\"\"\n\t\tThe height of this HTML element, in pixels.\n\t\t\"\"\"\n\t\treturn self._impl.height\n\t@property\n\tdef x(self):\n\t\t\"\"\"\n\t\tThe x-coordinate on the page of the top-left point of this HTML element.\n\t\t\"\"\"\n\t\treturn self._impl.x\n\t@property\n\tdef y(self):\n\t\t\"\"\"\n\t\tThe y-coordinate on the page of the top-left point of this HTML element.\n\t\t\"\"\"\n\t\treturn self._impl.y\n\t@property\n\tdef top_left(self):\n\t\t\"\"\"\n\t\tThe top left corner of this element, as a :py:class:`helium.Point`.\n\t\tThis point has exactly the coordinates given by this element's `.x` and\n\t\t`.y` properties. `top_left` is for instance useful for clicking at an\n\t\toffset of an element::\n\n\t\t\tclick(Button(\"OK\").top_left + (30, 15))\n\t\t\"\"\"\n\t\treturn self._impl.top_left\n\t@property\n\tdef web_element(self):\n\t\t\"\"\"\n\t\tThe Selenium WebElement corresponding to this element.\n\t\t\"\"\"\n\t\treturn self._impl.web_element\n\tdef __repr__(self):\n\t\tif self._is_bound():\n\t\t\ttry:\n\t\t\t\telement_html = self.web_element.get_attribute('outerHTML')\n\t\t\texcept NoSuchElementException:\n\t\t\t\t# This can happen when the element is not in the current iframe.\n\t\t\t\t# We could call `self._impl.first_occurrence.get_attribute(...)`\n\t\t\t\t# instead of `self.web_element.get_attribute(...)` to avoid it.\n\t\t\t\t# However, this would change the current frame. That seems too\n\t\t\t\t# surprising a side effect for a repr(...) call. So we instead\n\t\t\t\t# catch the error and fall back to the super implementation\n\t\t\t\t# further below.\n\t\t\t\tpass\n\t\t\telse:\n\t\t\t\treturn get_easily_readable_snippet(element_html)\n\t\treturn super(HTMLElement, self).__repr__()\n\nclass S(HTMLElement):\n\t\"\"\"\n\t:param selector: The selector used to identify the HTML element(s).\n\n\tA jQuery-style selector for identifying HTML elements by ID, name, CSS\n\tclass, CSS selector or XPath. For example: Say you have an element with\n\tID \"myId\" on a web page, such as ``<div id=\"myId\" .../>``.\n\tThen you can identify this element using ``S`` as follows::\n\n\t\tS(\"#myId\")\n\n\tThe parameter which you pass to ``S(...)`` is interpreted by Helium\n\taccording to these rules:\n\n\t * If it starts with an ``@``, then it identifies elements by HTML ``name``.\n\t   Eg. ``S(\"@btnName\")`` identifies an element with ``name=\"btnName\"``.\n\t * If it starts with ``//``, then Helium interprets it as an XPath.\n\t * Otherwise, Helium interprets it as a CSS selector. This in particular\n\t   lets you write ``S(\"#myId\")`` to identify an element with ``id=\"myId\"``,\n\t   or ``S(\".myClass\")`` to identify elements with ``class=\"myClass\"``.\n\n\t``S`` also makes it possible to read plain text data from a web page. For\n\texample, suppose you have a table of people's email addresses. Then you\n\tcan read the list of email addresses as follows::\n\n\t\temail_cells = find_all(S(\"table > tr > td\", below=\"Email\"))\n\t\temails = [cell.web_element.text for cell in email_cells]\n\n\tWhere ``email`` is the column header (``<th>Email</th>``). Similarly to\n\t``below`` and ``to_right_of``, the keyword parameters ``above`` and\n\t``to_left_of`` can be used to search for elements above and to the left\n\tof other web elements.\n\t\"\"\"\n\tdef __init__(self, selector, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None):\n\t\tsuper(S, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(selector)\n\nclass Text(HTMLElement):\n\t\"\"\"\n\tLets you identify any text or label on a web page. This is most useful for\n\tchecking whether a particular text exists::\n\n\t\tif Text(\"Do you want to proceed?\").exists():\n\t\t    click(\"Yes\")\n\n\t``Text`` also makes it possible to read plain text data from a web page. For\n\texample, suppose you have a table of people's email addresses. Then you\n\tcan read John's email addresses as follows::\n\n\t\tText(below=\"Email\", to_right_of=\"John\").value\n\n\tSimilarly to ``below`` and ``to_right_of``, the keyword parameters ``above``\n\tand ``to_left_of`` can be used to search for texts above and to the left of\n\tother web elements.\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, text=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(Text, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(text)\n\t@property\n\tdef value(self):\n\t\t\"\"\"\n\t\tReturns the current value of this Text object.\n\t\t\"\"\"\n\t\treturn self._impl.value\n\nclass Link(HTMLElement):\n\t\"\"\"\n\tLets you identify a link on a web page. A typical usage of ``Link`` is::\n\n\t\tclick(Link(\"Sign in\"))\n\n\tYou can also read a ``Link``'s properties. This is most typically used to\n\tcheck for a link's existence before clicking on it::\n\n\t\tif Link(\"Sign in\").exists():\n\t\t    click(Link(\"Sign in\"))\n\n\tWhen there are multiple occurrences of a link on a page, you can\n\tdisambiguate between them using the keyword parameters ``below``,\n\t``to_right_of``, ``above`` and ``to_left_of``. For instance::\n\n\t\tclick(Link(\"Block User\", to_right_of=\"John Doe\"))\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, text=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(Link, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(text)\n\t@property\n\tdef href(self):\n\t\t\"\"\"\n\t\tReturns the URL of the page the link goes to.\n\t\t\"\"\"\n\t\treturn self._impl.href\n\nclass ListItem(HTMLElement):\n\t\"\"\"\n\tLets you identify a list item (HTML ``<li>`` element) on a web page. This is\n\toften useful for interacting with elements of a navigation bar::\n\n\t\tclick(ListItem(\"News Feed\"))\n\n\tIn other cases such as an automated test, you might want to query the\n\tproperties of a ``ListItem``. For example, the following line checks whether\n\ta list item with text \"List item 1\" exists, and raises an error if not::\n\n\t\tassert ListItem(\"List item 1\").exists()\n\n\tWhen there are multiple occurrences of a list item on a page, you can\n\tdisambiguate between them using the keyword parameters ``below``,\n\t``to_right_of``, ``above`` and ``to_left_of``. For instance::\n\n\t\tclick(ListItem(\"List item 1\", below=\"My first list:\"))\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, text=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(ListItem, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(text)\n\nclass Button(HTMLElement):\n\t\"\"\"\n\tLets you identify a button on a web page. A typical usage of ``Button`` is::\n\n\t\tclick(Button(\"Log In\"))\n\n\t``Button`` also lets you read a button's properties. For example, the\n\tfollowing snippet clicks button \"OK\" only if it exists::\n\n\t\tif Button(\"OK\").exists():\n\t\t    click(Button(\"OK\"))\n\n\tWhen there are multiple occurrences of a button on a page, you can\n\tdisambiguate between them using the keyword parameters ``below``,\n\t``to_right_of``, ``above`` and ``to_left_of``. For instance::\n\n\t\tclick(Button(\"Log In\", below=TextField(\"Password\")))\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, text=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(Button, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(text)\n\tdef is_enabled(self):\n\t\t\"\"\"\n\t\tReturns true if this UI element can currently be interacted with.\n\t\t\"\"\"\n\t\treturn self._impl.is_enabled()\n\nclass Image(HTMLElement):\n\t\"\"\"\n\tLets you identify an image (HTML ``<img>`` element) on a web page.\n\tTypically, this is done via the image's alt text. For instance::\n\n\t\tclick(Image(alt=\"Helium Logo\"))\n\n\tYou can also query an image's properties. For example, the following snippet\n\tclicks on the image with alt text \"Helium Logo\" only if it exists::\n\n\t\tif Image(\"Helium Logo\").exists():\n\t\t    click(Image(\"Helium Logo\"))\n\n\tWhen there are multiple occurrences of an image on a page, you can\n\tdisambiguate between them using the keyword parameters ``below``,\n\t``to_right_of``, ``above`` and ``to_left_of``. For instance::\n\n\t\tclick(Image(\"Helium Logo\", to_left_of=ListItem(\"Download\")))\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, alt=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(Image, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(alt)\n\nclass TextField(HTMLElement):\n\t\"\"\"\n\tLets you identify a text field on a web page. This is most typically done to\n\tread the value of a text field. For example::\n\n\t\tTextField(\"First name\").value\n\n\tThis returns the value of the \"First name\" text field. If it is empty, the\n\tempty string \"\" is returned.\n\n\tWhen there are multiple occurrences of a text field on a page, you can\n\tdisambiguate between them using the keyword parameters ``below``,\n\t``to_right_of``, ``above`` and ``to_left_of``. For instance::\n\n\t\tTextField(\"Address line 1\", below=\"Billing Address:\").value\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, label=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(TextField, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(label)\n\t@property\n\tdef value(self):\n\t\t\"\"\"\n\t\tReturns the current value of this text field. '' if there is no value.\n\t\t\"\"\"\n\t\treturn self._impl.value\n\tdef is_enabled(self):\n\t\t\"\"\"\n\t\tReturns true if this UI element can currently be interacted with.\n\n\t\tThe difference between a text field being 'enabled' and 'editable' is\n\t\tmostly visual: If a text field is not enabled, it is usually greyed out,\n\t\twhereas if it is not editable it looks normal. See also ``is_editable``.\n\t\t\"\"\"\n\t\treturn self._impl.is_enabled()\n\tdef is_editable(self):\n\t\t\"\"\"\n\t\tReturns true if the value of this UI element can be modified.\n\n\t\tThe difference between a text field being 'enabled' and 'editable' is\n\t\tmostly visual: If a text field is not enabled, it is usually greyed out,\n\t\twhereas if it is not editable it looks normal. See also ``is_enabled``.\n\t\t\"\"\"\n\t\treturn self._impl.is_editable()\n\nclass ComboBox(HTMLElement):\n\t\"\"\"\n\tLets you identify a combo box on a web page. This can for instance be used\n\tto determine the current value of a combo box::\n\n\t\tComboBox(\"Language\").value\n\n\tA ComboBox may be *editable*, which means that it is possible to type in\n\tarbitrary values in addition to selecting from a predefined drop-down list\n\tof values. The property :py:func:`ComboBox.is_editable` can be used to\n\tdetermine whether this is the case for a particular combo box instance.\n\n\tWhen there are multiple occurrences of a combo box on a page, you can\n\tdisambiguate between them using the keyword parameters ``below``,\n\t``to_right_of``, ``above`` and ``to_left_of``. For instance::\n\n\t\tselect(ComboBox(to_right_of=\"John Doe\", below=\"Status\"), \"Active\")\n\n\tThis sets the Status of John Doe to Active on the page.\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, label=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(ComboBox, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(label)\n\tdef is_editable(self):\n\t\t\"\"\"\n\t\tReturns whether this combo box allows entering an arbitrary text in\n\t\taddition to selecting predefined values from a drop-down list.\n\t\t\"\"\"\n\t\treturn self._impl.is_editable()\n\t@property\n\tdef value(self):\n\t\t\"\"\"\n\t\tReturns the currently selected combo box value.\n\t\t\"\"\"\n\t\treturn self._impl.value\n\t@property\n\tdef options(self):\n\t\t\"\"\"\n\t\tReturns a list of all possible options available to choose from in the\n\t\tComboBox.\n\t\t\"\"\"\n\t\treturn self._impl.options\n\nclass CheckBox(HTMLElement):\n\t\"\"\"\n\tLets you identify a check box on a web page. To tick a currently unselected\n\tcheck box, use::\n\n\t\tclick(CheckBox(\"I agree\"))\n\n\t``CheckBox`` also lets you read the properties of a check box. For example,\n\tthe method :py:func:`CheckBox.is_checked` can be used to only click a check\n\tbox if it isn't already checked::\n\n\t\tif not CheckBox(\"I agree\").is_checked():\n\t\t    click(CheckBox(\"I agree\"))\n\n\tWhen there are multiple occurrences of a check box on a page, you can\n\tdisambiguate between them using the keyword parameters ``below``,\n\t``to_right_of``, ``above`` and ``to_left_of``. For instance::\n\n\t\tclick(CheckBox(\"Stay signed in\", below=Button(\"Sign in\")))\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, label=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(CheckBox, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(label)\n\tdef is_enabled(self):\n\t\t\"\"\"\n\t\tReturns True if this GUI element can currently be interacted with.\n\t\t\"\"\"\n\t\treturn self._impl.is_enabled()\n\tdef is_checked(self):\n\t\t\"\"\"\n\t\tReturns True if this GUI element is checked (selected).\n\t\t\"\"\"\n\t\treturn self._impl.is_checked()\n\nclass RadioButton(HTMLElement):\n\t\"\"\"\n\tLets you identify a radio button on a web page. To select a currently\n\tunselected radio button, use::\n\n\t\tclick(RadioButton(\"Windows\"))\n\n\t``RadioButton`` also lets you read the properties of a radio button. For\n\texample, the method :py:func:`RadioButton.is_selected` can be used to only\n\tclick a radio button if it isn't already selected::\n\n\t\tif not RadioButton(\"Windows\").is_selected():\n\t\t    click(RadioButton(\"Windows\"))\n\n\tWhen there are multiple occurrences of a radio button on a page, you can\n\tdisambiguate between them using the keyword parameters ``below``,\n\t``to_right_of``, ``above`` and ``to_left_of``. For instance::\n\n\t\tclick(RadioButton(\"I accept\", below=\"License Agreement\"))\n\t\"\"\"\n\tdef __init__(\n\t\t\tself, label=None, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(RadioButton, self).__init__(\n\t\t\tbelow=below, to_right_of=to_right_of, above=above,\n\t\t\tto_left_of=to_left_of\n\t\t)\n\t\tself._args.append(label)\n\tdef is_selected(self):\n\t\t\"\"\"\n\t\tReturns true if this radio button is selected.\n\t\t\"\"\"\n\t\treturn self._impl.is_selected()\n\nclass Window(GUIElement):\n\t\"\"\"\n\tLets you identify individual windows of the currently open browser session.\n\t\"\"\"\n\tdef __init__(self, title=None):\n\t\tsuper(Window, self).__init__()\n\t\tself._args.append(title)\n\t@property\n\tdef title(self):\n\t\t\"\"\"\n\t\tReturns the title of this Window.\n\t\t\"\"\"\n\t\treturn self._impl.title\n\t@property\n\tdef handle(self):\n\t\t\"\"\"\n\t\tReturns the Selenium driver window handle assigned to this window. Note\n\t\tthat this window handle is simply an abstract identifier and bears no\n\t\trelationship to the corresponding operating system handle (HWND on\n\t\tWindows).\n\t\t\"\"\"\n\t\treturn self._impl.handle\n\tdef __repr__(self):\n\t\tif self._is_bound():\n\t\t\treturn self._repr_constructor_args([self.title])\n\t\telse:\n\t\t\treturn super(Window, self).__repr__()\n\nclass Alert(GUIElement):\n\t\"\"\"\n\tLets you identify and interact with JavaScript alert boxes.\n\t\"\"\"\n\tdef __init__(self, search_text=None):\n\t\tsuper(Alert, self).__init__()\n\t\tself._args.append(search_text)\n\t@property\n\tdef text(self):\n\t\t\"\"\"\n\t\tThe text displayed in the alert box.\n\t\t\"\"\"\n\t\treturn self._impl.text\n\tdef accept(self):\n\t\t\"\"\"\n\t\tAccepts this alert. This typically corresponds to clicking the \"OK\"\n\t\tbutton inside the alert. The typical way to use this method is::\n\n\t\t\t>>> Alert().accept()\n\n\t\tThis accepts the currently open alert.\n\t\t\"\"\"\n\t\tself._impl.accept()\n\tdef dismiss(self):\n\t\t\"\"\"\n\t\tDismisses this alert. This typically corresponds to clicking the\n\t\t\"Cancel\" or \"Close\" button of the alert. The typical way to use this\n\t\tmethod is::\n\n\t\t\t>>> Alert().dismiss()\n\n\t\tThis dismisses the currently open alert.\n\t\t\"\"\"\n\t\tself._impl.dismiss()\n\tdef __repr__(self):\n\t\tif self._is_bound():\n\t\t\treturn self._repr_constructor_args([self.text])\n\t\telse:\n\t\t\treturn super(Alert, self).__repr__()\n\nclass Point(namedtuple('Point', ['x', 'y'])):\n\t\"\"\"\n\tA clickable point. To create a ``Point`` at an offset of an existing point,\n\tuse ``+`` and ``-``::\n\n\t\t>>> point = Point(x=10, y=25)\n\t\t>>> point + (10, 0)\n\t\tPoint(x=20, y=25)\n\t\t>>> point - (0, 10)\n\t\tPoint(x=10, y=15)\n\t\"\"\"\n\tdef __new__(cls, x=0, y=0):\n\t\treturn cls.__bases__[0].__new__(cls, x, y)\n\tdef __init__(self, x=0, y=0):\n\t\t# tuple is immutable so we can't do anything here. The initialization\n\t\t# happens in __new__(...) above.\n\t\tpass\n\t@property\n\tdef x(self):\n\t\t\"\"\"\n\t\tThe x coordinate of the point.\n\t\t\"\"\"\n\t\treturn self[0]\n\t@property\n\tdef y(self):\n\t\t\"\"\"\n\t\tThe y coordinate of the point.\n\t\t\"\"\"\n\t\treturn self[1]\n\tdef __eq__(self, other):\n\t\treturn (self.x, self.y) == other\n\tdef __ne__(self, other):\n\t\treturn not self == other\n\tdef __hash__(self):\n\t\treturn self.x + 7 * self.y\n\tdef __add__(self, delta):\n\t\tdx, dy = delta\n\t\treturn Point(self.x + dx, self.y + dy)\n\tdef __radd__(self, delta):\n\t\treturn self.__add__(delta)\n\tdef __sub__(self, delta):\n\t\tdx, dy = delta\n\t\treturn Point(self.x - dx, self.y - dy)\n\tdef __rsub__(self, delta):\n\t\tx, y = delta\n\t\treturn Point(x - self.x, y - self.y)\n\ndef switch_to(window):\n\t\"\"\"\n\t:param window: The title (string) of a browser window or a \\\n:py:class:`Window` object\n\n\tSwitches to the given browser window. For example::\n\n\t\tswitch_to(\"Google\")\n\n\tThis searches for a browser window whose title contains \"Google\", and\n\tactivates it.\n\n\tIf there are multiple windows with the same title, then you can use\n\t:py:func:`find_all` to find all open windows, pick out the one you want and\n\tpass that to ``switch_to``. For example, the following snippet switches to\n\tthe first window in the list of open windows::\n\n\t\tswitch_to(find_all(Window())[0])\n\t\"\"\"\n\t_get_api_impl().switch_to_impl(window)\n\ndef kill_browser():\n\t\"\"\"\n\tCloses the current browser with all associated windows and potentially open\n\tdialogs. Dialogs opened as a response to the browser closing (eg. \"Are you\n\tsure you want to leave this page?\") are also ignored and closed.\n\n\tThis function is most commonly used to close the browser at the end of an\n\tautomation run::\n\n\t\tstart_chrome()\n\t\t...\n\t\t# Close Chrome:\n\t\tkill_browser()\n\t\"\"\"\n\t_get_api_impl().kill_browser_impl()\n\ndef highlight(element):\n\t\"\"\"\n\t:param element: The element to highlight.\n\n\tHighlights the given element on the webpage by drawing a red rectangle\n\taround it. This is useful for debugging purposes. For example::\n\n\t\thighlight(\"Helium\")\n\t\thighlight(Button(\"Sign in\"))\n\t\"\"\"\n\t_get_api_impl().highlight_impl(element)\n\ndef _get_api_impl():\n\tglobal _API_IMPL\n\tif _API_IMPL is None:\n\t\t_API_IMPL = APIImpl()\n\treturn _API_IMPL\n\n_API_IMPL = None"
  },
  {
    "path": "helium/_impl/__init__.py",
    "content": "from copy import copy\nfrom helium._impl.match_type import PREFIX_IGNORE_CASE\nfrom helium._impl.selenium_wrappers import WebElementWrapper, \\\n\tWebDriverWrapper, FrameIterator, FramesChangedWhileIterating\nfrom helium._impl.util.dictionary import inverse\nfrom helium._impl.util.system import is_windows, get_canonical_os_name\nfrom helium._impl.util.xpath import lower, predicate, predicate_or\nfrom inspect import getfullargspec, ismethod, isfunction\nfrom selenium.common.exceptions import UnexpectedAlertPresentException, \\\n\tElementNotVisibleException, MoveTargetOutOfBoundsException, \\\n\tWebDriverException, StaleElementReferenceException, \\\n\tNoAlertPresentException, NoSuchWindowException\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.firefox.service import Service as ServiceFirefox\nfrom selenium.webdriver.remote.webelement import WebElement\nfrom selenium.webdriver.support.wait import WebDriverWait\nfrom selenium.webdriver.support.ui import Select\nfrom selenium.webdriver import Chrome, ChromeOptions, Firefox, FirefoxOptions\nfrom time import sleep, time\n\nimport atexit\nimport re\n\ndef might_spawn_window(f):\n\tdef f_decorated(self, *args, **kwargs):\n\t\tdriver = self.require_driver()\n\t\tif driver.is_ie() and AlertImpl(driver).exists():\n\t\t\t# Accessing .window_handles in IE when an alert is present raises an\n\t\t\t# UnexpectedAlertPresentException. When DesiredCapability\n\t\t\t# 'unexpectedAlertBehaviour' is not 'ignore' (the default is\n\t\t\t# 'dismiss'), this leads to the alert being closed. Since we don't\n\t\t\t# want to unintentionally close alert dialogs, we therefore do not\n\t\t\t# access .window_handles in IE when an alert is present.\n\t\t\treturn f(self, *args, **kwargs)\n\t\twindow_handles_before = driver.window_handles[:]\n\t\tresult = f(self, *args, **kwargs)\n\t\t# As above, don't access .window_handles in IE if an alert is present:\n\t\tif not (driver.is_ie() and AlertImpl(driver).exists()):\n\t\t\tif driver.is_firefox():\n\t\t\t\t# Unlike Chrome, Firefox does not wait for new windows to open.\n\t\t\t\t# Give it a little time to do so:\n\t\t\t\tsleep(.2)\n\t\t\tnew_window_handles = [\n\t\t\t\th for h in driver.window_handles\n\t\t\t\tif h not in window_handles_before\n\t\t\t]\n\t\t\tif new_window_handles:\n\t\t\t\tdriver.switch_to.window(new_window_handles[0])\n\t\treturn result\n\treturn f_decorated\n\ndef handle_unexpected_alert(f):\n\tdef f_decorated(*args, **kwargs):\n\t\ttry:\n\t\t\treturn f(*args, **kwargs)\n\t\texcept UnexpectedAlertPresentException:\n\t\t\traise UnexpectedAlertPresentException(\n\t\t\t\t\"This command is not supported when an alert is present. To \"\n\t\t\t\t\"accept the alert (this usually corresponds to clicking 'OK') \"\n\t\t\t\t\"use `Alert().accept()`. To dismiss the alert (ie. 'cancel' \"\n\t\t\t\t\"it), use `Alert().dismiss()`. If the alert contains a text \"\n\t\t\t\t\"field, you can use write(...) to set its value. \"\n\t\t\t\t\"Eg.: `write('hi there!')`.\"\n\t\t\t)\n\treturn f_decorated\n\nclass APIImpl:\n\tDRIVER_REQUIRED_MESSAGE = \\\n\t\t\"This operation requires a browser window. Please call one of \" \\\n\t\t\"the following functions first:\\n\" \\\n\t\t\" * start_chrome()\\n\" \\\n\t\t\" * start_firefox()\\n\" \\\n\t\t\" * set_driver(...)\"\n\tdef __init__(self):\n\t\tself.driver = None\n\tdef start_firefox_impl(\n\t\tself, url=None, headless=False, options=None, profile=None\n\t):\n\t\tfirefox_driver = self._start_firefox_driver(headless, options, profile)\n\t\treturn self._start(firefox_driver, url)\n\tdef _start_firefox_driver(self, headless, options, profile):\n\t\tfirefox_options = FirefoxOptions() if options is None else options\n\t\tif headless:\n\t\t\tfirefox_options.add_argument('--headless')\n\t\tkwargs = {\n\t\t\t'options': firefox_options\n\t\t}\n\t\tif profile:\n\t\t\tfirefox_options.profile = profile\n\t\tservice_log_path = 'nul' if is_windows() else '/dev/null'\n\t\tservice = ServiceFirefox(log_path=service_log_path)\n\t\tresult = Firefox(service=service, **kwargs)\n\t\treturn result\n\tdef start_chrome_impl(\n\t\tself, url=None, headless=False, maximize=False, options=None\n\t):\n\t\tchrome_driver = self._start_chrome_driver(headless, maximize, options)\n\t\treturn self._start(chrome_driver, url)\n\tdef _start_chrome_driver(self, headless, maximize, options):\n\t\tchrome_options = self._get_chrome_options(headless, maximize, options)\n\t\tresult = Chrome(options=chrome_options)\n\t\tatexit.register(self._kill_service, result.service)\n\t\treturn result\n\tdef _get_chrome_options(self, headless, maximize, options):\n\t\tresult = ChromeOptions() if options is None else options\n\t\t# Prevent Chrome's debug logs from appearing in our console window:\n\t\tresult.add_experimental_option('excludeSwitches', ['enable-logging'])\n\t\tif headless:\n\t\t\tresult.add_argument('--headless')\n\t\telif maximize:\n\t\t\tresult.add_argument('--start-maximized')\n\n\t\t# Chrome 140.0.7339.185 or earlier introduced password leak detection.\n\t\t# Writing leaked credentials into an input field sometimes brings up a\n\t\t# blocking browser notification \"The password you just used was found in\n\t\t# a data breach\". Prevent this:\n\t\tprefs = dict(result.experimental_options.get('prefs', {}))\n\t\tif 'profile.password_manager_leak_detection' not in prefs:\n\t\t\tprefs['profile.password_manager_leak_detection'] = False\n\t\t\tresult.add_experimental_option('prefs', prefs)\n\n\t\treturn result\n\tdef _kill_service(self, service):\n\t\told = service.send_remote_shutdown_command\n\t\tservice.send_remote_shutdown_command = lambda: None\n\t\ttry:\n\t\t\tservice.stop()\n\t\tfinally:\n\t\t\tservice.send_remote_shutdown_command = old\n\tdef _start(self, browser, url=None):\n\t\tself.set_driver_impl(browser)\n\t\tif url is not None:\n\t\t\tself.go_to_impl(url)\n\t\treturn self.get_driver_impl()\n\t@might_spawn_window\n\t@handle_unexpected_alert\n\tdef go_to_impl(self, url):\n\t\tif '://' not in url:\n\t\t\turl = 'http://' + url\n\t\tself.require_driver().get(url)\n\tdef set_driver_impl(self, driver):\n\t\tself.driver = WebDriverWrapper(driver)\n\tdef get_driver_impl(self):\n\t\tif self.driver is not None:\n\t\t\treturn self.driver.unwrap()\n\t@might_spawn_window\n\t@handle_unexpected_alert\n\tdef write_impl(self, text, into=None):\n\t\tif into is not None:\n\t\t\tfrom helium import GUIElement\n\t\t\tif isinstance(into, GUIElement):\n\t\t\t\tinto = into._impl\n\t\tself._handle_alerts(\n\t\t\tself._write_no_alert, self._write_with_alert, text, into=into\n\t\t)\n\tdef _write_no_alert(self, text, into=None):\n\t\tif into:\n\t\t\tif isinstance(into, str):\n\t\t\t\tinto = TextFieldImpl(self.require_driver(), into)\n\t\t\tdef _write(elt):\n\t\t\t\tif hasattr(elt, 'clear') and callable(elt.clear):\n\t\t\t\t\telt.clear()\n\t\t\t\telt.send_keys(text)\n\t\t\tself._manipulate(into, _write)\n\t\telse:\n\t\t\tself.require_driver().switch_to.active_element.send_keys(text)\n\tdef _write_with_alert(self, text, into=None):\n\t\tif into is None:\n\t\t\tinto = AlertImpl(self.require_driver())\n\t\tif not isinstance(into, AlertImpl):\n\t\t\traise UnexpectedAlertPresentException(\n\t\t\t\t\"into=%r is not allowed when an alert is present.\" % into\n\t\t\t)\n\t\tinto._write(text)\n\tdef _handle_alerts(self, no_alert, with_alert, *args, **kwargs):\n\t\tdriver = self.require_driver()\n\t\tif not AlertImpl(driver).exists():\n\t\t\treturn no_alert(*args, **kwargs)\n\t\treturn with_alert(*args, **kwargs)\n\t@might_spawn_window\n\t@handle_unexpected_alert\n\tdef press_impl(self, key):\n\t\tself.require_driver().switch_to.active_element.send_keys(key)\n\tdef click_impl(self, element):\n\t\tself._perform_mouse_action(element, self._click)\n\tdef doubleclick_impl(self, element):\n\t\tself._perform_mouse_action(element, self._doubleclick)\n\tdef hover_impl(self, element):\n\t\tself._perform_mouse_action(element, self._hover)\n\tdef rightclick_impl(self, element):\n\t\tself._perform_mouse_action(element, self._rightclick)\n\tdef press_mouse_on_impl(self, element):\n\t\tself._perform_mouse_action(element, self._press_mouse_on)\n\tdef release_mouse_over_impl(self, element):\n\t\tself._perform_mouse_action(element, self._release_mouse_over)\n\tdef _click(self, selenium_elt, offset):\n\t\tself._move_to_element(selenium_elt, offset).click().perform()\n\tdef _doubleclick(self, selenium_elt, offset):\n\t\tself._move_to_element(selenium_elt, offset).double_click().perform()\n\tdef _hover(self, selenium_elt, offset):\n\t\tself._move_to_element(selenium_elt, offset).perform()\n\tdef _rightclick(self, selenium_elt, offset):\n\t\tself._move_to_element(selenium_elt, offset).context_click().perform()\n\tdef _press_mouse_on(self, selenium_elt, offset):\n\t\tself._move_to_element(selenium_elt, offset).click_and_hold().perform()\n\tdef _release_mouse_over(self, selenium_elt, offset):\n\t\tself._move_to_element(selenium_elt, offset).release().perform()\n\tdef _move_to_element(self, element, offset):\n\t\tresult = self.require_driver().action()\n\t\tif offset is not None:\n\t\t\tresult.move_to_element_with_offset(\n\t\t\t\telement,\n\t\t\t\toffset[0] - element.size['width'] / 2,\n\t\t\t\toffset[1] - element.size['height'] / 2\n\t\t\t)\n\t\telse:\n\t\t\tresult.move_to_element(element)\n\t\treturn result\n\tdef drag_impl(self, element, to):\n\t\twith DragHelper(self) as drag_helper:\n\t\t\tself._perform_mouse_action(element, drag_helper.start_dragging)\n\t\t\tself._perform_mouse_action(to, drag_helper.drop_on_target)\n\t@might_spawn_window\n\t@handle_unexpected_alert\n\tdef _perform_mouse_action(self, element, action):\n\t\telement, offset = self._unwrap_clickable_element(element)\n\t\tself._manipulate(element, lambda wew: action(wew.unwrap(), offset))\n\tdef _unwrap_clickable_element(self, elt):\n\t\tfrom helium import HTMLElement, Point\n\t\toffset = None\n\t\tif isinstance(elt, str):\n\t\t\telt = ClickableText(self.require_driver(), elt)\n\t\telif isinstance(elt, HTMLElement):\n\t\t\telt = elt._impl\n\t\telif isinstance(elt, Point):\n\t\t\telt, offset = self._point_to_element_and_offset(elt)\n\t\treturn elt, offset\n\tdef _point_to_element_and_offset(self, point):\n\t\tdriver = self.require_driver()\n\t\telement = WebElementWrapper(driver.execute_script(\n\t\t\t'return document.elementFromPoint(%r, %r);' % (point.x, point.y)\n\t\t))\n\t\toffset = point - (element.location.left, element.location.top)\n\t\tif offset == (0, 0) and driver.is_firefox():\n\t\t\t# In some CSS settings (eg. test_point.html), the (0, 0) point of\n\t\t\t# buttons in Firefox is not clickable! The reason for this is that\n\t\t\t# Firefox styles buttons to not be perfect squares, but have an\n\t\t\t# indent in the corners. This workaround makes `click(btn.top_left)`\n\t\t\t# work even when this happens:\n\t\t\toffset = (1, 1)\n\t\treturn element, offset\n\t@handle_unexpected_alert\n\tdef find_all_impl(self, predicate):\n\t\twhile True:\n\t\t\ttry:\n\t\t\t\treturn [\n\t\t\t\t\tpredicate.with_impl(bound_gui_elt_impl)\n\t\t\t\t\tfor bound_gui_elt_impl in predicate._impl.iter_all()\n\t\t\t\t]\n\t\t\texcept FramesChangedWhileIterating:\n\t\t\t\t# Try again.\n\t\t\t\tpass\n\tdef scroll_down_impl(self, num_pixels):\n\t\tself._scroll_by(0, num_pixels)\n\tdef scroll_up_impl(self, num_pixels):\n\t\tself._scroll_by(0, -num_pixels)\n\tdef scroll_right_impl(self, num_pixels):\n\t\tself._scroll_by(num_pixels, 0)\n\tdef scroll_left_impl(self, num_pixels):\n\t\tself._scroll_by(-num_pixels, 0)\n\t@handle_unexpected_alert\n\tdef _scroll_by(self, dx_pixels, dy_pixels):\n\t\tself.require_driver().execute_script(\n\t\t\t'window.scrollBy(arguments[0], arguments[1]);', dx_pixels, dy_pixels\n\t\t)\n\t@might_spawn_window\n\t@handle_unexpected_alert\n\tdef select_impl(self, combo_box, value):\n\t\tfrom helium import ComboBox\n\t\tif isinstance(combo_box, str):\n\t\t\tcombo_box = ComboBoxImpl(self.require_driver(), combo_box)\n\t\telif isinstance(combo_box, ComboBox):\n\t\t\tcombo_box = combo_box._impl\n\t\tdef _select(web_element):\n\t\t\tif isinstance(web_element, WebElementWrapper):\n\t\t\t\tweb_element = web_element.unwrap()\n\t\t\tSelect(web_element).select_by_visible_text(value)\n\t\tself._manipulate(combo_box, _select)\n\tdef _manipulate(self, gui_or_web_elt, action):\n\t\tdriver = self.require_driver()\n\t\tif hasattr(gui_or_web_elt, 'perform') \\\n\t\t\tand callable(gui_or_web_elt.perform):\n\t\t\tdriver.last_manipulated_element = gui_or_web_elt.perform(action)\n\t\telse:\n\t\t\tif isinstance(gui_or_web_elt, WebElement):\n\t\t\t\tgui_or_web_elt = WebElementWrapper(gui_or_web_elt)\n\t\t\taction(gui_or_web_elt)\n\t\t\tdriver.last_manipulated_element = gui_or_web_elt\n\t@handle_unexpected_alert\n\tdef drag_file_impl(self, file_path, to):\n\t\tto, _ = self._unwrap_clickable_element(to)\n\t\tdrag_and_drop = DragAndDropFile(self.require_driver(), file_path)\n\t\tdrag_and_drop.begin()\n\t\ttry:\n\t\t\t# Some web apps (Gmail in particular) only register for the 'drop'\n\t\t\t# event when user has dragged the file over the document. We\n\t\t\t# therefore simulate this dragging over the document first:\n\t\t\tdrag_and_drop.drag_over_document()\n\t\t\tself._manipulate(to, lambda elt: drag_and_drop.drop_on(elt))\n\t\tfinally:\n\t\t\tdrag_and_drop.end()\n\t@might_spawn_window\n\t@handle_unexpected_alert\n\tdef attach_file_impl(self, file_path, to=None):\n\t\tfrom helium import Point\n\t\tdriver = self.require_driver()\n\t\tif to is None:\n\t\t\tto = FileInput(driver)\n\t\telif isinstance(to, str):\n\t\t\tto = FileInput(driver, to)\n\t\telif isinstance(to, Point):\n\t\t\tto, _ = self._point_to_element_and_offset(to)\n\t\tself._manipulate(to, lambda elt: elt.send_keys(file_path))\n\tdef refresh_impl(self):\n\t\tself._handle_alerts(\n\t\t\tself._refresh_no_alert, self._refresh_with_alert\n\t\t)\n\tdef _refresh_no_alert(self):\n\t\tself.require_driver().refresh()\n\tdef _refresh_with_alert(self):\n\t\tAlertImpl(self.require_driver()).accept()\n\t\tself._refresh_no_alert()\n\tdef wait_until_impl(self, condition_fn, timeout_secs=10, interval_secs=0.5):\n\t\tif ismethod(condition_fn):\n\t\t\tis_bound = condition_fn.__self__ is not None\n\t\t\targs_spec = getfullargspec(condition_fn).args\n\t\t\tunfilled_args = len(args_spec) - (1 if is_bound else 0)\n\t\telse:\n\t\t\tif not isfunction(condition_fn):\n\t\t\t\tcondition_fn = condition_fn.__call__\n\t\t\targs_spec = getfullargspec(condition_fn).args\n\t\t\tunfilled_args = len(args_spec)\n\t\tdef condition(driver):\n\t\t\ttry:\n\t\t\t\treturn condition_fn(driver) if unfilled_args else condition_fn()\n\t\t\texcept FramesChangedWhileIterating:\n\t\t\t\treturn False\n\t\twait = WebDriverWait(\n\t\t\tself.require_driver().unwrap(), timeout_secs,\n\t\t\tpoll_frequency=interval_secs\n\t\t)\n\t\twait.until(condition)\n\t@handle_unexpected_alert\n\tdef switch_to_impl(self, window):\n\t\tdriver = self.require_driver()\n\t\tfrom helium import Window\n\t\tif isinstance(window, str):\n\t\t\twindow = WindowImpl(driver, window)\n\t\telif isinstance(window, Window):\n\t\t\twindow = window._impl\n\t\tdriver.switch_to.window(window.handle)\n\tdef kill_browser_impl(self):\n\t\tself.require_driver().quit()\n\t\tself.driver = None\n\t@handle_unexpected_alert\n\tdef highlight_impl(self, element):\n\t\tdriver = self.require_driver()\n\t\tfrom helium import HTMLElement, Text\n\t\tif isinstance(element, str):\n\t\t\telement = Text(element)\n\t\tif isinstance(element, HTMLElement):\n\t\t\telement = element._impl\n\t\ttry:\n\t\t\telement = element.first_occurrence\n\t\texcept AttributeError:\n\t\t\tpass\n\t\tprevious_style = element.get_attribute(\"style\")\n\t\tif isinstance(element, WebElementWrapper):\n\t\t\telement = element.unwrap()\n\t\tdriver.execute_script(\n\t\t\t\"arguments[0].setAttribute(\"\n\t\t\t\t\"'style', 'border: 2px solid red; font-weight: bold;'\"\n\t\t\t\");\", element\n\t\t)\n\t\tdriver.execute_script(\n\t\t\t\"var target = arguments[0];\"\n\t\t\t\"var previousStyle = arguments[1];\"\n\t\t\t\"setTimeout(\"\n\t\t\t\t\"function() {\"\n\t\t\t\t\t\"target.setAttribute('style', previousStyle);\"\n\t\t\t\t\"}, 2000\"\n\t\t\t\");\", element, previous_style\n\t\t)\n\tdef require_driver(self):\n\t\tif not self.driver:\n\t\t\traise RuntimeError(self.DRIVER_REQUIRED_MESSAGE)\n\t\treturn self.driver\n\nclass DragHelper:\n\tdef __init__(self, api_impl):\n\t\tself.api_impl = api_impl\n\t\tself.is_html_5_drag = None\n\tdef __enter__(self):\n\t\tself._execute_script(\n\t\t\t\"window.helium = {};\"\n\t\t\t\"window.helium.dragHelper = {\"\n\t\t\t\"    createEvent: function(type) {\"\n\t\t\t\"        var event = document.createEvent('CustomEvent');\"\n\t\t\t\"        event.initCustomEvent(type, true, true, null);\"\n\t\t\t\"        event.dataTransfer = {\"\n\t\t\t\"            data: {},\"\n\t\t\t\"            setData: function(type, val) {\"\n\t\t\t\"                this.data[type] = val;\"\n\t\t\t\"            },\"\n\t\t\t\"            getData: function(type) {\"\n\t\t\t\"                return this.data[type];\"\n\t\t\t\"            }\"\n\t\t\t\"        };\"\n\t\t\t\"        return event;\"\n\t\t\t\"    }\"\n\t\t\t\"};\"\n\t\t)\n\t\treturn self\n\tdef start_dragging(self, element, offset):\n\t\tif self._attempt_html_5_drag(element):\n\t\t\tself.is_html_5_drag = True\n\t\telse:\n\t\t\tself.api_impl._press_mouse_on(element, offset)\n\tdef drop_on_target(self, target, offset):\n\t\tif self.is_html_5_drag:\n\t\t\tself._complete_html_5_drag(target)\n\t\telse:\n\t\t\tself.api_impl._release_mouse_over(target, offset)\n\tdef _attempt_html_5_drag(self, element_to_drag):\n\t\treturn self._execute_script(\n\t\t\t\"var source = arguments[0];\"\n\t\t\t\"function getDraggableParent(element) {\"\n\t\t\t\"    var previousParent = null;\"\n\t\t\t\"    while (element != null && element != previousParent) {\"\n\t\t\t\"        previousParent = element;\"\n\t\t\t\"        if ('draggable' in element) {\"\n\t\t\t\"            var draggable = element.draggable;\"\n\t\t\t\"            if (draggable === true)\"\n\t\t\t\"                return element;\"\n\t\t\t\"            if (typeof draggable == 'string' \"\n\t\t\t\"                    || draggable instanceof String)\"\n\t\t\t\"                if (draggable.toLowerCase() == 'true')\"\n\t\t\t\"                    return element;\"\n\t\t\t\"        }\"\n\t\t\t\"        element = element.parentNode;\"\n\t\t\t\"    }\"\n\t\t\t\"    return null;\"\n\t\t\t\"}\"\n\t\t\t\"var draggableParent = getDraggableParent(source);\"\n\t\t\t\"if (draggableParent == null)\"\n\t\t\t\"    return false;\"\n\t\t\t\"window.helium.dragHelper.draggedElement = draggableParent;\"\n\t\t\t\"var dragStart = window.helium.dragHelper.createEvent('dragstart');\"\n\t\t\t\"source.dispatchEvent(dragStart);\"\n\t\t\t\"window.helium.dragHelper.dataTransfer = dragStart.dataTransfer;\"\n\t\t\t\"return true;\",\n\t\t\telement_to_drag\n\t\t)\n\tdef _complete_html_5_drag(self, on):\n\t\tself._execute_script(\n\t\t\t\"var target = arguments[0];\"\n\t\t\t\"var drop = window.helium.dragHelper.createEvent('drop');\"\n\t\t\t\"drop.dataTransfer = window.helium.dragHelper.dataTransfer;\"\n\t\t\t\"target.dispatchEvent(drop);\"\n\t\t\t\"var dragEnd = window.helium.dragHelper.createEvent('dragend');\"\n\t\t\t\"dragEnd.dataTransfer = window.helium.dragHelper.dataTransfer;\"\n\t\t\t\"window.helium.dragHelper.draggedElement.dispatchEvent(dragEnd);\",\n\t\t\ton\n\t\t)\n\tdef __exit__(self, *_):\n\t\tself._execute_script(\"delete window.helium;\")\n\tdef _execute_script(self, script, *args):\n\t\treturn self.api_impl.require_driver().execute_script(script, *args)\n\nclass DragAndDropFile:\n\tdef __init__(self, driver, file_path):\n\t\tself.driver = driver\n\t\tself.file_path = file_path\n\t\tself.file_input_element = None\n\t\tself.dragover_event = None\n\tdef begin(self):\n\t\tself._create_file_input_element()\n\t\ttry:\n\t\t\tself.file_input_element.send_keys(self.file_path)\n\t\texcept:\n\t\t\tself.end()\n\t\t\traise\n\tdef _create_file_input_element(self):\n\t\t# The input needs to be visible to Selenium to allow sending keys to it\n\t\t# in Firefox and IE.\n\t\t# According to http://stackoverflow.com/questions/6101461/\n\t\t# Selenium criteria whether an element is visible or not are the\n\t\t# following:\n\t\t#  - visibility != hidden\n\t\t#  - display != none (is also checked against every parent element)\n\t\t#  - opacity != 0\n\t\t#  - height and width are both > 0\n\t\t#  - for an input, the attribute type != hidden\n\t\t# So let's make sure its all good!\n\t\tself.file_input_element = self.driver.execute_script(\n\t\t\t\"var input = document.createElement('input');\"\n\t\t\t\"input.type = 'file';\"\n\t\t\t\"input.style.display = 'block';\"\n\t\t\t\"input.style.opacity = '1';\"\n\t\t\t\"input.style.visibility = 'visible';\"\n\t\t\t\"input.style.height = '1px';\"\n\t\t\t\"input.style.width = '1px';\"\n\t\t\t\"if (document.body.childElementCount > 0) { \"\n\t\t\t\"  document.body.insertBefore(input, document.body.childNodes[0]);\"\n\t\t\t\"} else { \"\n\t\t\t\"  document.body.appendChild(input);\"\n\t\t\t\"}\"\n\t\t\t\"return input;\"\n\t\t)\n\tdef drag_over_document(self):\n\t\t# According to the HTML5 spec, we need to dispatch the dragenter event\n\t\t# once, and then the dragover event continuously, every 350+-200ms:\n\t\t# http://www.w3.org/html/wg/drafts/html/master/editing.html#current-drag\n\t\t# -operation\n\t\t# Especially IE implements this spec very tightly, and considers the\n\t\t# dragging to be over if no dragover event occurs for more than ~1sec.\n\t\t# We thus need to ensure that we keep dispatching the dragover event.\n\n\t\t# This line used to read `_dispatch_event(..., to='document')`. However,\n\t\t# this doesn't work when adding a photo to a tweet on Twitter.\n\t\t# Dispatching the event to document.body fixes this, and also works for\n\t\t# Gmail:\n\t\tself._dispatch_event('dragenter', to='document.body')\n\t\tself.dragover_event = self._prepare_continuous_event(\n\t\t\t'dragover', 'document', interval_msecs=300\n\t\t)\n\t\tself.dragover_event.start()\n\tdef _dispatch_event(self, event_name, to):\n\t\tscript, args = self._prepare_dispatch_event(event_name, to)\n\t\tself.driver.execute_script(script, *args)\n\tdef _prepare_continuous_event(self, event_name, to, interval_msecs):\n\t\tscript, args = self._prepare_dispatch_event(event_name, to)\n\t\treturn JavaScriptInterval(self.driver, script, args, interval_msecs)\n\tdef _prepare_dispatch_event(self, event_name, to):\n\t\tscript = \\\n\t\t\t\"var files = arguments[0].files;\" \\\n\t\t\t\"var items = [];\" \\\n\t\t\t\"var types = [];\" \\\n\t\t\t\"for (var i = 0; i < files.length; i++) {\" \\\n\t\t\t\"   items[i] = {kind: 'file', type: files[i].type};\" \\\n\t\t\t\"   types[i] = 'Files';\" \\\n\t\t\t\"}\" \\\n\t\t\t\"var event = document.createEvent('CustomEvent');\" \\\n\t\t\t\"event.initCustomEvent(arguments[1], true, true, 0);\" \\\n\t\t\t\"event.dataTransfer = {\" \\\n\t\t\t\"\tfiles: files,\" \\\n\t\t\t\"\titems: items,\" \\\n\t\t\t\"\ttypes: types\" \\\n\t\t\t\"};\" \\\n\t\t\t\"arguments[2].dispatchEvent(event);\"\n\t\tif isinstance(to, str):\n\t\t\tscript = script.replace('arguments[2]', to)\n\t\t\targs = self.file_input_element, event_name,\n\t\telse:\n\t\t\targs = self.file_input_element, event_name, to.unwrap()\n\t\treturn script, args\n\tdef drop_on(self, target):\n\t\tself.dragover_event.stop()\n\t\tself._dispatch_event('drop', to=target)\n\tdef end(self):\n\t\tif self.file_input_element is not None:\n\t\t\tself.driver.execute_script(\n\t\t\t\t\"arguments[0].parentNode.removeChild(arguments[0]);\",\n\t\t\t\tself.file_input_element\n\t\t\t)\n\t\tself.file_input_element = None\n\nclass JavaScriptInterval:\n\tdef __init__(self, driver, script, args, interval_msecs):\n\t\tself.driver = driver\n\t\tself.script = script\n\t\tself.args = args\n\t\tself.interval_msecs = interval_msecs\n\t\tself._interval_id = None\n\tdef start(self):\n\t\tsetinterval_script = (\n\t\t\t\"var originalArguments = arguments;\"\n\t\t\t\"return setInterval(function() {\"\n\t\t\t\"\targuments = originalArguments;\"\n\t\t\t\"\t%s\"\n\t\t\t\"}, %d);\"\n\t\t) % (self.script, self.interval_msecs)\n\t\tself._interval_id = \\\n\t\t\tself.driver.execute_script(setinterval_script, *self.args)\n\tdef stop(self):\n\t\tself.driver.execute_script(\n\t\t\t\"clearInterval(arguments[0]);\", self._interval_id\n\t\t)\n\t\tself._interval_id = None\n\nclass GUIElementImpl:\n\tdef __init__(self, driver):\n\t\tself._bound_occurrence = None\n\t\tself._driver = driver\n\tdef iter_all(self, ignore_frame_changes=False):\n\t\tif self._is_bound():\n\t\t\tyield self\n\t\telse:\n\t\t\twhile True:\n\t\t\t\ttry:\n\t\t\t\t\tfor occurrence in self.iter_all_occurrences():\n\t\t\t\t\t\tyield self.bound_to_occurrence(occurrence)\n\t\t\t\texcept FramesChangedWhileIterating:\n\t\t\t\t\tif not ignore_frame_changes:\n\t\t\t\t\t\traise\n\t\t\t\tbreak\n\tdef _is_bound(self):\n\t\treturn self._bound_occurrence is not None\n\tdef iter_all_occurrences(self):\n\t\traise NotImplementedError()\n\tdef bound_to_occurrence(self, occurrence):\n\t\tresult = copy(self)\n\t\tresult._bound_occurrence = occurrence\n\t\treturn result\n\tdef exists(self):\n\t\ttry:\n\t\t\tnext(self.iter_all(ignore_frame_changes=True))\n\t\texcept StopIteration:\n\t\t\treturn False\n\t\telse:\n\t\t\treturn True\n\t@property\n\tdef first_occurrence(self):\n\t\tif not self._is_bound():\n\t\t\tself._bind_to_first_occurrence()\n\t\treturn self._bound_occurrence\n\tdef _bind_to_first_occurrence(self):\n\t\tself.perform(lambda _: None)\n\t\t# _perform_no_wait(...) below now sets _bound_occurrence.\n\tdef perform(self, action):\n\t\tfrom helium import Config\n\t\tend_time = time() + Config.implicit_wait_secs\n\t\t# Try to perform `action` at least once:\n\t\tresult = self._perform_no_wait(action)\n\t\twhile result is None and time() < end_time:\n\t\t\tresult = self._perform_no_wait(action)\n\t\tif result is not None:\n\t\t\treturn result\n\t\traise LookupError()\n\tdef _perform_no_wait(self, action):\n\t\tfor bound_gui_elt_impl in self.iter_all(ignore_frame_changes=True):\n\t\t\toccurrence = bound_gui_elt_impl.first_occurrence\n\t\t\ttry:\n\t\t\t\taction(occurrence)\n\t\t\texcept Exception as e:\n\t\t\t\tif not self.should_ignore_exception(e):\n\t\t\t\t\traise\n\t\t\telse:\n\t\t\t\tself._bound_occurrence = occurrence\n\t\t\t\treturn occurrence\n\tdef should_ignore_exception(self, exception):\n\t\tif isinstance(exception, ElementNotVisibleException):\n\t\t\treturn True\n\t\tif isinstance(exception, MoveTargetOutOfBoundsException):\n\t\t\treturn True\n\t\tif isinstance(exception, StaleElementReferenceException):\n\t\t\treturn True\n\t\tif isinstance(exception, WebDriverException):\n\t\t\tmsg = exception.msg\n\t\t\tif 'is not clickable at point' in msg \\\n\t\t\t\tand 'Other element would receive the click' in msg:\n\t\t\t\t# This can happen when the element has moved.\n\t\t\t\treturn True\n\t\treturn False\n\nclass HTMLElementImpl(GUIElementImpl):\n\tdef __init__(\n\t\t\tself, driver, below=None, to_right_of=None, above=None,\n\t\t\tto_left_of=None\n\t):\n\t\tsuper(HTMLElementImpl, self).__init__(driver)\n\t\tself.below = self._unwrap_element(below)\n\t\tself.to_right_of = self._unwrap_element(to_right_of)\n\t\tself.above = self._unwrap_element(above)\n\t\tself.to_left_of = self._unwrap_element(to_left_of)\n\t\tself.matches = PREFIX_IGNORE_CASE()\n\tdef find_anywhere_in_curr_frame(self):\n\t\traise NotImplementedError()\n\t@property\n\tdef width(self):\n\t\treturn self.first_occurrence.location.width\n\t@property\n\tdef height(self):\n\t\treturn self.first_occurrence.location.height\n\t@property\n\tdef x(self):\n\t\treturn self.first_occurrence.location.left\n\t@property\n\tdef y(self):\n\t\treturn self.first_occurrence.location.top\n\t@property\n\tdef top_left(self):\n\t\tfrom helium import Point\n\t\treturn Point(self.x, self.y)\n\t@property\n\tdef web_element(self):\n\t\treturn self.first_occurrence.unwrap()\n\tdef iter_all_occurrences(self):\n\t\tself._handle_closed_window()\n\t\tself._driver.switch_to.default_content()\n\t\talready_yielded = set()\n\t\tfor frame_index in FrameIterator(self._driver):\n\t\t\tfor occurrence in self._find_all_in_curr_frame():\n\t\t\t\tif occurrence.target in already_yielded:\n\t\t\t\t\t# We have seen this element before, but its frame had a\n\t\t\t\t\t# different index. This means that the frames have changed.\n\t\t\t\t\t# Abort:\n\t\t\t\t\treturn\n\t\t\t\toccurrence.frame_index = frame_index\n\t\t\t\tyield occurrence\n\t\t\t\talready_yielded.add(occurrence.target)\n\tdef _handle_closed_window(self):\n\t\twindow_handles = self._driver.window_handles\n\t\ttry:\n\t\t\tcurr_window_handle = self._driver.current_window_handle\n\t\texcept NoSuchWindowException:\n\t\t\twindow_has_been_closed = True\n\t\telse:\n\t\t\twindow_has_been_closed = curr_window_handle not in window_handles\n\t\tif window_has_been_closed:\n\t\t\tself._driver.switch_to.window(window_handles[0])\n\tdef _find_all_in_curr_frame(self):\n\t\tsearch_regions = self._get_search_regions_in_curr_frame()\n\t\tfor occurrence in self.find_anywhere_in_curr_frame():\n\t\t\tif not occurrence.is_displayed():\n\t\t\t\tcontinue\n\t\t\tif self._is_in_any_search_region(occurrence, search_regions):\n\t\t\t\tyield occurrence\n\tdef _get_search_regions_in_curr_frame(self):\n\t\tresult = []\n\t\tif self.below:\n\t\t\tresult.append([\n\t\t\t\telt.location.is_above\n\t\t\t\tfor elt in self._resolve_in_curr_frame(self.below)\n\t\t\t])\n\t\tif self.to_right_of:\n\t\t\tresult.append([\n\t\t\t\telt.location.is_to_left_of\n\t\t\t\tfor elt in self._resolve_in_curr_frame(self.to_right_of)\n\t\t\t])\n\t\tif self.above:\n\t\t\tresult.append([\n\t\t\t\telt.location.is_below\n\t\t\t\tfor elt in self._resolve_in_curr_frame(self.above)\n\t\t\t])\n\t\tif self.to_left_of:\n\t\t\tresult.append([\n\t\t\t\telt.location.is_to_right_of\n\t\t\t\tfor elt in self._resolve_in_curr_frame(self.to_left_of)\n\t\t\t])\n\t\treturn result\n\tdef _resolve_in_curr_frame(self, element):\n\t\tif element._is_bound():\n\t\t\treturn [element.first_occurrence]\n\t\treturn element._find_all_in_curr_frame()\n\tdef _is_in_any_search_region(self, element, search_regions):\n\t\tfor direction in search_regions:\n\t\t\tfound = False\n\t\t\tfor search_region in direction:\n\t\t\t\tif search_region(element.location):\n\t\t\t\t\tfound = True\n\t\t\t\t\tbreak\n\t\t\tif not found:\n\t\t\t\treturn False\n\t\treturn True\n\tdef _is_enabled(self):\n\t\t\"\"\"\n\t\tUseful for subclasses.\n\t\t\"\"\"\n\t\treturn self.first_occurrence.get_attribute('disabled') is None\n\tdef _unwrap_element(self, element):\n\t\tif isinstance(element, str):\n\t\t\treturn TextImpl(self._driver, element)\n\t\tfrom helium import HTMLElement\n\t\tif isinstance(element, HTMLElement):\n\t\t\treturn element._impl\n\t\treturn element\n\nclass SImpl(HTMLElementImpl):\n\tdef __init__(self, driver, selector, **kwargs):\n\t\tsuper(SImpl, self).__init__(driver, **kwargs)\n\t\tself.selector = selector\n\tdef find_anywhere_in_curr_frame(self):\n\t\twrap = lambda web_elements: list(map(WebElementWrapper, web_elements))\n\t\tif self.selector.startswith('@'):\n\t\t\treturn wrap(self._driver.find_elements(By.NAME, self.selector[1:]))\n\t\tif self.selector.startswith('//'):\n\t\t\treturn wrap(self._driver.find_elements(By.XPATH, self.selector))\n\t\treturn wrap(self._driver.find_elements(By.CSS_SELECTOR, self.selector))\n\nclass HTMLElementIdentifiedByXPath(HTMLElementImpl):\n\tdef find_anywhere_in_curr_frame(self):\n\t\tx_path = self.get_xpath()\n\t\treturn self._sort_search_result(\n\t\t\tlist(map(\n\t\t\t\tWebElementWrapper, self._driver.find_elements(By.XPATH, x_path)\n\t\t\t))\n\t\t)\n\tdef _sort_search_result(self, search_result):\n\t\tkeys_to_result_items = []\n\t\tfor web_elt in search_result:\n\t\t\ttry:\n\t\t\t\tkey = self.get_sort_index(web_elt)\n\t\t\texcept StaleElementReferenceException:\n\t\t\t\tpass\n\t\t\telse:\n\t\t\t\tkeys_to_result_items.append((key, web_elt))\n\t\tsort_key = lambda tpl: tpl[0]\n\t\tkeys_to_result_items.sort(key=sort_key)\n\t\tresult_item = lambda tpl: tpl[1]\n\t\treturn list(map(result_item, keys_to_result_items))\n\tdef get_xpath(self):\n\t\traise NotImplementedError()\n\tdef get_sort_index(self, web_element):\n\t\treturn self._driver.get_distance_to_last_manipulated(web_element) + 1\n\nclass HTMLElementContainingText(HTMLElementIdentifiedByXPath):\n\tdef __init__(self, driver, text=None, **kwargs):\n\t\tsuper(HTMLElementContainingText, self).__init__(driver, **kwargs)\n\t\tself.search_text = text\n\tdef get_xpath(self):\n\t\txpath_base = \"//\" + self.get_xpath_node_selector() + \\\n\t\t\t\t\t predicate(self.matches.xpath('.', self.search_text))\n\t\treturn '%s[not(self::script)][not(.%s)]' % (xpath_base, xpath_base)\n\tdef get_xpath_node_selector(self):\n\t\treturn '*'\n\nclass TextImpl(HTMLElementContainingText):\n\tdef __init__(self, driver, text=None, include_free_text=True, **kwargs):\n\t\tsuper(TextImpl, self).__init__(driver, text, **kwargs)\n\t\tself.include_free_text = include_free_text\n\t@property\n\tdef value(self):\n\t\treturn self.first_occurrence.text\n\tdef get_xpath(self):\n\t\tbutton_impl = ButtonImpl(self._driver, self.search_text)\n\t\tlink_impl = LinkImpl(self._driver, self.search_text)\n\t\tcomponents = [\n\t\t\tself._get_search_text_xpath(),\n\t\t\tbutton_impl.get_input_button_xpath(),\n\t\t\tlink_impl.get_xpath()\n\t\t]\n\t\tif self.search_text and self.include_free_text:\n\t\t\tcomponents.append(\n\t\t\t\tFreeText(self._driver, self.search_text).get_xpath()\n\t\t\t)\n\t\treturn ' | '.join(components)\n\tdef _get_search_text_xpath(self):\n\t\tif self.search_text:\n\t\t\tresult = super(TextImpl, self).get_xpath()\n\t\telse:\n\t\t\tno_descendant_with_same_text = \\\n\t\t\t\t\"not(.//*[normalize-space(.)=normalize-space(self::*)])\"\n\t\t\tresult = '//*[text() and %s]' % no_descendant_with_same_text\n\t\treturn result + \"[not(self::option)]\" + \\\n\t\t       (\"\" if self.include_free_text else \"[count(*) <= 1]\")\n\nclass FreeText(HTMLElementContainingText):\n\tdef get_xpath_node_selector(self):\n\t\treturn 'text()'\n\tdef get_xpath(self):\n\t\treturn super(FreeText, self).get_xpath() + '/..'\n\nclass LinkImpl(HTMLElementContainingText):\n\tdef get_xpath_node_selector(self):\n\t\treturn 'a'\n\tdef get_xpath(self):\n\t\treturn super(LinkImpl, self).get_xpath() + ' | ' + \\\n\t\t\t   \"//a\" + \\\n\t\t\t   predicate(self.matches.xpath('@title', self.search_text)) + \\\n\t\t\t   ' | ' + \"//*[@role='link']\" + \\\n\t\t\t   predicate(self.matches.xpath('.', self.search_text))\n\t@property\n\tdef href(self):\n\t\treturn self.web_element.get_attribute('href')\n\nclass ListItemImpl(HTMLElementContainingText):\n\tdef get_xpath_node_selector(self):\n\t\treturn 'li'\n\nclass ButtonImpl(HTMLElementContainingText):\n\tdef get_xpath_node_selector(self):\n\t\treturn 'button'\n\tdef is_enabled(self):\n\t\taria_disabled = self.first_occurrence.get_attribute('aria-disabled')\n\t\treturn self._is_enabled() \\\n\t\t\tand (not aria_disabled or aria_disabled.lower() == 'false')\n\tdef get_xpath(self):\n\t\thas_aria_label = self.matches.xpath('@aria-label', self.search_text)\n\t\thas_text = self.matches.xpath('.', self.search_text)\n\t\thas_text_or_aria_label = predicate_or(has_aria_label, has_text)\n\t\treturn ' | '.join([\n\t\t\tsuper(ButtonImpl, self).get_xpath(), self.get_input_button_xpath(),\n\t\t\t\"//*[@role='button']\" + has_text_or_aria_label,\n\t\t\t\"//button\" + predicate(has_aria_label)\n\t\t])\n\tdef get_input_button_xpath(self):\n\t\tif self.search_text:\n\t\t\thas_value = self.matches.xpath('@value', self.search_text)\n\t\t\thas_label = self.matches.xpath('@label', self.search_text)\n\t\t\thas_aria_label = self.matches.xpath('@aria-label', self.search_text)\n\t\t\thas_title = self.matches.xpath('@title', self.search_text)\n\t\t\thas_text = \\\n\t\t\t\tpredicate_or(has_value, has_label, has_aria_label, has_title)\n\t\telse:\n\t\t\thas_text = ''\n\t\treturn \"//input[@type='submit' or @type='button']\" + has_text\n\nclass ImageImpl(HTMLElementIdentifiedByXPath):\n\tdef __init__(self, driver, alt, **kwargs):\n\t\tsuper(ImageImpl, self).__init__(driver, **kwargs)\n\t\tself.alt = alt\n\tdef get_xpath(self):\n\t\treturn \"//img\" + predicate(self.matches.xpath('@alt', self.alt))\n\nclass LabelledElement(HTMLElementImpl):\n\tSECONDARY_SEARCH_DIMENSION_PENALTY_FACTOR = 1.5\n\tdef __init__(self, driver, label=None, **kwargs):\n\t\tsuper(LabelledElement, self).__init__(driver, **kwargs)\n\t\tself.label = label\n\tdef find_anywhere_in_curr_frame(self):\n\t\tif not self.label:\n\t\t\tresult = self._find_elts()\n\t\telse:\n\t\t\tlabels = TextImpl(\n\t\t\t\tself._driver, self.label, include_free_text=False\n\t\t\t).find_anywhere_in_curr_frame()\n\t\t\tif labels:\n\t\t\t\tresult = list(self._filter_elts_belonging_to_labels(\n\t\t\t\t\tself._find_elts(), labels\n\t\t\t\t))\n\t\t\telse:\n\t\t\t\tresult = self._find_elts_by_free_text()\n\t\treturn sorted(result, key=self._driver.get_distance_to_last_manipulated)\n\tdef _find_elts(self, xpath=None):\n\t\tif xpath is None:\n\t\t\txpath = self.get_xpath()\n\t\treturn list(map(\n\t\t\tWebElementWrapper, self._driver.find_elements(By.XPATH, xpath)\n\t\t))\n\tdef _find_elts_by_free_text(self):\n\t\telt_types = [\n\t\t\txpath.strip().lstrip('/') for xpath in self.get_xpath().split('|')\n\t\t]\n\t\tlabels = '//text()' + predicate(self.matches.xpath('.', self.label))\n\t\txpath = ' | '.join(\n\t\t\t[(labels + '/%s::' + elt_type + '[1]')\n\t\t\t % ('preceding-sibling'\n\t\t\t    if 'checkbox' in elt_type or 'radio' in elt_type\n\t\t\t    else 'following')\n\t\t\t for elt_type in elt_types]\n\t\t)\n\t\treturn self._find_elts(xpath)\n\tdef get_xpath(self):\n\t\traise NotImplementedError()\n\tdef get_primary_search_direction(self):\n\t\treturn 'to_right_of'\n\tdef get_secondary_search_direction(self):\n\t\treturn 'below'\n\tdef _filter_elts_belonging_to_labels(self, all_elts, labels):\n\t\tfor label, elt in self._get_labels_with_explicit_elts(all_elts, labels):\n\t\t\tyield elt\n\t\t\tlabels.remove(label)\n\t\t\tall_elts.remove(elt)\n\t\tlabels_to_elts = self._get_related_elts(all_elts, labels)\n\t\tlabels_to_elts = self._ensure_at_most_one_label_per_elt(labels_to_elts)\n\t\tself._retain_closest(labels_to_elts)\n\t\tfor elts_for_label in list(labels_to_elts.values()):\n\t\t\tassert len(elts_for_label) <= 1\n\t\t\tif elts_for_label:\n\t\t\t\tyield next(iter(elts_for_label))\n\tdef _get_labels_with_explicit_elts(self, all_elts, labels):\n\t\tfor label in labels:\n\t\t\ttry:\n\t\t\t\tif label.tag_name == 'label':\n\t\t\t\t\tlabel_target = label.get_attribute('for')\n\t\t\t\t\tif label_target:\n\t\t\t\t\t\tfor elt in all_elts:\n\t\t\t\t\t\t\telt_id = elt.get_attribute('id')\n\t\t\t\t\t\t\tif elt_id.lower() == label_target.lower():\n\t\t\t\t\t\t\t\tyield label, elt\n\t\t\texcept Exception as e:\n\t\t\t\tif not self.should_ignore_exception(e):\n\t\t\t\t\traise\n\tdef _get_related_elts(self, all_elts, labels):\n\t\tresult = {}\n\t\tfor label in labels:\n\t\t\tfor elt in all_elts:\n\t\t\t\ttry:\n\t\t\t\t\tif self._are_related(elt, label):\n\t\t\t\t\t\tif label not in result:\n\t\t\t\t\t\t\tresult[label] = set()\n\t\t\t\t\t\tresult[label].add(elt)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tif not self.should_ignore_exception(e):\n\t\t\t\t\t\traise\n\t\treturn result\n\tdef _are_related(self, elt, label):\n\t\tif elt.location.intersects(label.location):\n\t\t\treturn True\n\t\tprim_search_dir = self.get_primary_search_direction()\n\t\tsec_search_dir = self.get_secondary_search_direction()\n\t\treturn label.location.distance_to(elt.location) <= 150 and (\n\t\t\telt.location.is_in_direction(prim_search_dir, label.location) or\n\t\t\telt.location.is_in_direction(sec_search_dir, label.location)\n\t\t)\n\tdef _ensure_at_most_one_label_per_elt(self, labels_to_elts):\n\t\telts_to_labels = inverse(labels_to_elts)\n\t\tself._retain_closest(elts_to_labels)\n\t\treturn inverse(elts_to_labels)\n\tdef _retain_closest(self, pivots_to_elts):\n\t\tfor pivot, elts in list(pivots_to_elts.items()):\n\t\t\tif elts:\n\t\t\t\tclosest = self._find_closest(pivot, elts)\n\t\t\t\tif closest:\n\t\t\t\t\tpivots_to_elts[pivot] = {closest}\n\tdef _find_closest(self, to_pivot, among_elts):\n\t\tdistances = []\n\t\tfor elt in among_elts:\n\t\t\ttry:\n\t\t\t\tdistance = self._compute_distance(elt, to_pivot)\n\t\t\texcept Exception as e:\n\t\t\t\tif not self.should_ignore_exception(e):\n\t\t\t\t\traise\n\t\t\telse:\n\t\t\t\tdistances.append((distance, elt))\n\t\tif distances:\n\t\t\t# Provide `key=` to prevent a TypeError that happens when Python\n\t\t\t# attempts to sort on the second items of the tuples when the first\n\t\t\t# items are equal.\n\t\t\treturn sorted(distances, key=lambda tpl: tpl[0])[0][1]\n\tdef _compute_distance(self, elt_1, elt_2):\n\t\tloc_1 = elt_1.location\n\t\tloc_2 = elt_2.location\n\t\tif loc_1.is_in_direction(self.get_secondary_search_direction(), loc_2):\n\t\t\tfactor = self.SECONDARY_SEARCH_DIMENSION_PENALTY_FACTOR\n\t\telse:\n\t\t\tfactor = 1\n\t\treturn factor * loc_1.distance_to(loc_2)\n\nclass CompositeElement(HTMLElementImpl):\n\tdef __init__(self, driver, *args, **kwargs):\n\t\tsuper(CompositeElement, self).__init__(driver, **kwargs)\n\t\tself.args = [driver] + list(args)\n\t\tself.kwargs = kwargs\n\t\tself._first_element = None\n\t@property\n\tdef first_element(self):\n\t\tif self._first_element is None:\n\t\t\tself._bind_to_first_occurrence()\n\t\t\t# find_anywhere_in_curr_frame() below now sets _first_element\n\t\treturn self._first_element\n\tdef find_anywhere_in_curr_frame(self):\n\t\talready_yielded = []\n\t\tfor element in self.get_elements():\n\t\t\tfor bound_gui_elt_impl in element.find_anywhere_in_curr_frame():\n\t\t\t\tif self._first_element is None:\n\t\t\t\t\tself._first_element = element\n\t\t\t\tif bound_gui_elt_impl not in already_yielded:\n\t\t\t\t\tyield bound_gui_elt_impl\n\t\t\t\t\talready_yielded.append(bound_gui_elt_impl)\n\tdef get_elements(self):\n\t\tfor element_type in self.get_element_types():\n\t\t\tyield element_type(*self.args, **self.kwargs)\n\tdef get_element_types(self):\n\t\traise NotImplementedError()\n\nclass ClickableText(CompositeElement):\n\tdef get_element_types(self):\n\t\treturn [ButtonImpl, TextImpl, ImageImpl]\n\nclass TextFieldImpl(CompositeElement):\n\tdef get_element_types(self):\n\t\treturn [\n\t\t\tStandardTextFieldWithPlaceholder, StandardTextFieldWithLabel,\n\t\t\tAriaTextFieldWithLabel\n\t\t]\n\t@property\n\tdef value(self):\n\t\treturn self.first_element.value\n\tdef is_enabled(self):\n\t\treturn self.first_element.is_enabled()\n\tdef is_editable(self):\n\t\treturn self.first_element.is_editable()\n\nclass StandardTextFieldWithLabel(LabelledElement):\n\t@property\n\tdef value(self):\n\t\treturn self.first_occurrence.get_attribute('value') or ''\n\tdef is_enabled(self):\n\t\treturn self._is_enabled()\n\tdef is_editable(self):\n\t\treturn self.first_occurrence.get_attribute('readOnly') is None\n\tdef get_xpath(self):\n\t\treturn \\\n\t\t\t\"//input[%s='text' or %s='email' or %s='password' or %s='number' \" \\\n\t\t\t \"or %s='date' or %s='time' or %s='tel' or string-length(@type)=0]\"\\\n\t\t\t % ((lower('@type'), ) * 7) + \\\n\t\t\t \" | //textarea | //*[@contenteditable='true']\"\n\nclass AriaTextFieldWithLabel(LabelledElement):\n\t@property\n\tdef value(self):\n\t\treturn self.first_occurrence.text\n\tdef is_enabled(self):\n\t\treturn self._is_enabled()\n\tdef is_editable(self):\n\t\treturn self.first_occurrence.get_attribute('readOnly') is None\n\tdef get_xpath(self):\n\t\treturn \"//*[@role='textbox']\"\n\nclass StandardTextFieldWithPlaceholder(HTMLElementIdentifiedByXPath):\n\tdef __init__(self, driver, label, **kwargs):\n\t\tsuper(StandardTextFieldWithPlaceholder, self).__init__(driver, **kwargs)\n\t\tself.label = label\n\t@property\n\tdef value(self):\n\t\treturn self.first_occurrence.get_attribute('value') or ''\n\tdef is_enabled(self):\n\t\treturn self._is_enabled()\n\tdef is_editable(self):\n\t\treturn self.first_occurrence.get_attribute('readOnly') is None\n\tdef get_xpath(self):\n\t\treturn \"(%s)%s\" % (\n\t\t\tStandardTextFieldWithLabel(self.label).get_xpath(),\n\t\t\tpredicate(self.matches.xpath('@placeholder', self.label))\n\t\t)\n\nclass FileInput(LabelledElement):\n\tdef get_xpath(self):\n\t\treturn \"//input[@type='file']\"\n\nclass ComboBoxImpl(CompositeElement):\n\tdef get_element_types(self):\n\t\treturn [ComboBoxIdentifiedByDisplayedValue, ComboBoxIdentifiedByLabel]\n\tdef is_editable(self):\n\t\treturn self.first_occurrence.tag_name != 'select'\n\t@property\n\tdef value(self):\n\t\tselected_value = self._select_driver.first_selected_option\n\t\tif selected_value:\n\t\t\treturn selected_value.text\n\t\treturn None\n\t@property\n\tdef options(self):\n\t\treturn [option.text for option in self._select_driver.options]\n\t@property\n\tdef _select_driver(self):\n\t\treturn Select(self.web_element)\n\nclass ComboBoxIdentifiedByLabel(LabelledElement):\n\tdef get_xpath(self):\n\t\treturn \"//select | //input[@list]\"\n\nclass ComboBoxIdentifiedByDisplayedValue(HTMLElementContainingText):\n\tdef get_xpath_node_selector(self):\n\t\treturn 'option'\n\tdef get_xpath(self):\n\t\toption_xpath = \\\n\t\t\tsuper(ComboBoxIdentifiedByDisplayedValue, self).get_xpath()\n\t\treturn option_xpath + '/ancestor::select[1]'\n\tdef find_anywhere_in_curr_frame(self):\n\t\tall_cbs_with_a_matching_value = super(\n\t\t\tComboBoxIdentifiedByDisplayedValue, self\n\t\t).find_anywhere_in_curr_frame()\n\t\tresult = []\n\t\tfor cb in all_cbs_with_a_matching_value:\n\t\t\tfor selected_option in Select(cb.unwrap()).all_selected_options:\n\t\t\t\tif self.matches.text(selected_option.text, self.search_text):\n\t\t\t\t\tresult.append(cb)\n\t\t\t\t\tbreak\n\t\treturn result\n\nclass CheckBoxImpl(LabelledElement):\n\tdef is_enabled(self):\n\t\treturn self._is_enabled()\n\tdef is_checked(self):\n\t\treturn self.first_occurrence.get_attribute('checked') is not None\n\tdef get_xpath(self):\n\t\treturn \"//input[@type='checkbox']\"\n\tdef get_primary_search_direction(self):\n\t\treturn 'to_left_of'\n\tdef get_secondary_search_direction(self):\n\t\treturn 'to_right_of'\n\nclass RadioButtonImpl(LabelledElement):\n\tdef is_selected(self):\n\t\treturn self.first_occurrence.get_attribute('checked') is not None\n\tdef get_xpath(self):\n\t\treturn \"//input[@type='radio']\"\n\tdef get_primary_search_direction(self):\n\t\treturn 'to_left_of'\n\tdef get_secondary_search_direction(self):\n\t\treturn 'to_right_of'\n\nclass WindowImpl(GUIElementImpl):\n\tdef __init__(self, driver, title=None):\n\t\tsuper(WindowImpl, self).__init__(driver)\n\t\tself.search_title = title\n\tdef iter_all_occurrences(self):\n\t\tresult_scores = []\n\t\tfor handle in self._driver.window_handles:\n\t\t\twindow = WindowImpl.SeleniumWindow(self._driver, handle)\n\t\t\tif self.search_title is None:\n\t\t\t\tresult_scores.append((0, window))\n\t\t\telse:\n\t\t\t\ttitle = window.title\n\t\t\t\tif title.startswith(self.search_title):\n\t\t\t\t\tscore = len(title) - len(self.search_title)\n\t\t\t\t\tresult_scores.append((score, window))\n\t\tscore = lambda tpl: tpl[0]\n\t\tresult_scores.sort(key=score)\n\t\tfor score, window in result_scores:\n\t\t\tyield window\n\t@property\n\tdef title(self):\n\t\treturn self.first_occurrence.title\n\t@property\n\tdef handle(self):\n\t\treturn self.first_occurrence.handle\n\tclass SeleniumWindow:\n\t\tdef __init__(self, driver, handle):\n\t\t\tself.driver = driver\n\t\t\tself.handle = handle\n\t\t\tself._window_handle_before = None\n\t\t@property\n\t\tdef title(self):\n\t\t\twith self:\n\t\t\t\treturn self.driver.title\n\t\tdef __enter__(self):\n\t\t\ttry:\n\t\t\t\tself._window_handle_before = self.driver.current_window_handle\n\t\t\texcept NoSuchWindowException as window_closed:\n\t\t\t\tdo_switch = True\n\t\t\telse:\n\t\t\t\tdo_switch = self._window_handle_before != self.handle\n\t\t\tif do_switch:\n\t\t\t\tself.driver.switch_to.window(self.handle)\n\t\tdef __exit__(self, *_):\n\t\t\tif self._window_handle_before and \\\n\t\t\t\tself.driver.current_window_handle != self._window_handle_before:\n\t\t\t\tself.driver.switch_to.window(self._window_handle_before)\n\nclass AlertImpl(GUIElementImpl):\n\tdef __init__(self, driver, search_text=None):\n\t\tsuper(AlertImpl, self).__init__(driver)\n\t\tself.search_text = search_text\n\tdef iter_all_occurrences(self):\n\t\ttry:\n\t\t\tresult = self._driver.switch_to.alert\n\t\t\ttext = result.text\n\t\t\tif self.search_text is None or text.startswith(self.search_text):\n\t\t\t\tyield result\n\t\texcept NoAlertPresentException:\n\t\t\tpass\n\t@property\n\tdef text(self):\n\t\treturn self.first_occurrence.text\n\tdef accept(self):\n\t\tfirst_occurrence = self.first_occurrence\n\t\ttry:\n\t\t\tfirst_occurrence.accept()\n\t\texcept WebDriverException as e:\n\t\t\t# Attempt to work around Selenium issue 3544:\n\t\t\t# https://code.google.com/p/selenium/issues/detail?id=3544\n\t\t\tmsg = e.msg\n\t\t\tif msg and re.match(\n\t\t\t\t\tr\"a\\.document\\.getElementsByTagName\\([^\\)]*\\)\\[0\\] is \"\n\t\t\t\t\tr\"undefined\", msg\n\t\t\t):\n\t\t\t\tsleep(0.25)\n\t\t\t\tfirst_occurrence.accept()\n\t\t\telse:\n\t\t\t\traise\n\tdef dismiss(self):\n\t\tself.first_occurrence.dismiss()\n\tdef _write(self, text):\n\t\tself.first_occurrence.send_keys(text)\n"
  },
  {
    "path": "helium/_impl/match_type.py",
    "content": "from helium._impl.util.xpath import lower, replace_nbsp\n\nclass MatchType:\n\tdef xpath(self, value, text):\n\t\traise NotImplementedError()\n\tdef text(self, value, text):\n\t\traise NotImplementedError()\n\nclass PREFIX_IGNORE_CASE(MatchType):\n\tdef xpath(self, value, text):\n\t\tif not text:\n\t\t\treturn ''\n\t\t# Asterisks '*' are sometimes used to mark required fields. Eg.:\n\t\t# <label for=\"title\"><span class=\"red-txt\">*</span> Title:</label>\n\t\t# The starts-with filter below would be too strict to include such\n\t\t# matches. To get around this, we ignore asterisks unless the searched\n\t\t# text itself contains one.\n\t\tif '*' in text:\n\t\t\tstrip_asterisks = value\n\t\telse:\n\t\t\tstrip_asterisks = \"translate(%s, '*', '')\" % value\n\n\t\t# if text contains apostrophes (single quotes) then they need to be\n\t\t# treated with care\n\t\tif \"'\" in text:\n\t\t\ttext = \"concat('%s')\" % (\"',\\\"'\\\",'\".join(text.split(\"'\")))\n\t\telse:\n\t\t\ttext = \"'%s'\" % text\n\n\t\treturn \"starts-with(normalize-space(%s), %s)\" % (\n\t\t\tlower(replace_nbsp(strip_asterisks)), text.lower()\n\t\t)\n\tdef text(self, value, text):\n\t\tif not text:\n\t\t\treturn True\n\t\treturn value.lower().lstrip().startswith(text.lower())"
  },
  {
    "path": "helium/_impl/selenium_wrappers.py",
    "content": "from helium._impl.util.geom import Rectangle\nfrom selenium.common.exceptions import StaleElementReferenceException, \\\n\tNoSuchFrameException, WebDriverException, NoSuchElementException\nfrom selenium.webdriver.common.action_chains import ActionChains\nfrom urllib.error import URLError\nimport sys\n\nclass Wrapper:\n\tdef __init__(self, target):\n\t\tself.target = target\n\tdef __getattr__(self, item):\n\t\treturn getattr(self.target, item)\n\tdef unwrap(self):\n\t\treturn self.target\n\tdef __hash__(self):\n\t\treturn hash(self.target)\n\tdef __eq__(self, other):\n\t\treturn self.target == other.target\n\tdef __ne__(self, other):\n\t\treturn not self == other\n\nclass WebDriverWrapper(Wrapper):\n\tdef __init__(self, target):\n\t\tsuper(WebDriverWrapper, self).__init__(target)\n\t\tself.last_manipulated_element = None\n\tdef action(self):\n\t\treturn ActionChains(self.target)\n\tdef get_distance_to_last_manipulated(self, web_element):\n\t\tif not self.last_manipulated_element:\n\t\t\treturn 0\n\t\ttry:\n\t\t\tif hasattr(self.last_manipulated_element, 'location'):\n\t\t\t\tlast_location = self.last_manipulated_element.location\n\t\t\t\treturn last_location.distance_to(web_element.location)\n\t\texcept StaleElementReferenceException:\n\t\t\treturn 0\n\t\telse:\n\t\t\t# No .location. This happens when last_manipulated_element is an\n\t\t\t# Alert or a Window.\n\t\t\treturn 0\n\tdef is_firefox(self):\n\t\treturn self.browser_name == 'firefox'\n\t@property\n\tdef browser_name(self):\n\t\treturn self.target.capabilities['browserName']\n\tdef is_ie(self):\n\t\treturn self.browser_name == 'internet explorer'\n\ndef _translate_url_errors_caused_by_server_shutdown(f):\n\tdef f_decorated(*args, **kwargs):\n\t\ttry:\n\t\t\treturn f(*args, **kwargs)\n\t\texcept URLError as url_error:\n\t\t\tif _is_caused_by_server_shutdown(url_error):\n\t\t\t\traise StaleElementReferenceException(\n\t\t\t\t\t'The Selenium server this element belonged to is no longer '\n\t\t\t\t\t'available.'\n\t\t\t\t)\n\t\t\telse:\n\t\t\t\traise\n\treturn f_decorated\n\ndef _is_caused_by_server_shutdown(url_error):\n\ttry:\n\t\tCONNECTION_REFUSED = 10061\n\t\treturn url_error.args[0][0] == CONNECTION_REFUSED\n\texcept (IndexError, TypeError):\n\t\treturn False\n\ndef handle_element_being_in_other_frame(f):\n\tdef f_decorated(self, *args, **kwargs):\n\t\tif not self.frame_index:\n\t\t\treturn f(self, *args, **kwargs)\n\t\ttry:\n\t\t\treturn f(self, *args, **kwargs)\n\t\texcept (StaleElementReferenceException, NoSuchElementException) \\\n\t\t\tas original_exc:\n\t\t\ttry:\n\t\t\t\tframe_iterator = FrameIterator(self.target.parent)\n\t\t\t\tframe_iterator.switch_to_frame(self.frame_index)\n\t\t\texcept NoSuchFrameException:\n\t\t\t\traise original_exc\n\t\t\telse:\n\t\t\t\treturn f(self, *args, **kwargs)\n\treturn f_decorated\n\nclass WebElementWrapper:\n\tdef __init__(self, target, frame_index=None):\n\t\tself.target = target\n\t\tself.frame_index = frame_index\n\t\tself._cached_location = None\n\t@property\n\t@handle_element_being_in_other_frame\n\t@_translate_url_errors_caused_by_server_shutdown\n\tdef location(self):\n\t\tif self._cached_location is None:\n\t\t\t# Cache access to web_element.location as it's expensive:\n\t\t\tlocation = self.target.location\n\t\t\tx, y = location['x'], location['y']\n\t\t\t# Cache access to web_element.size as it's expensive:\n\t\t\tsize = self.target.size\n\t\t\twidth, height = size['width'], size['height']\n\t\t\tself._cached_location = Rectangle(x, y, width, height)\n\t\treturn self._cached_location\n\tdef is_displayed(self):\n\t\ttry:\n\t\t\treturn self.target.is_displayed() and self.location.intersects(\n\t\t\t\tRectangle(0, 0, sys.maxsize, sys.maxsize)\n\t\t\t)\n\t\texcept StaleElementReferenceException:\n\t\t\treturn False\n\t@handle_element_being_in_other_frame\n\tdef get_attribute(self, attr_name):\n\t\treturn self.target.get_attribute(attr_name)\n\t@property\n\t@handle_element_being_in_other_frame\n\tdef text(self):\n\t\treturn self.target.text\n\t@handle_element_being_in_other_frame\n\tdef clear(self):\n\t\tself.target.clear()\n\t@handle_element_being_in_other_frame\n\tdef send_keys(self, keys):\n\t\tself.target.send_keys(keys)\n\t@property\n\t@handle_element_being_in_other_frame\n\tdef tag_name(self):\n\t\treturn self.target.tag_name\n\tdef unwrap(self):\n\t\treturn self.target\n\tdef __repr__(self):\n\t\treturn '<%s>%s</%s>' % (self.tag_name, self.target.text, self.tag_name)\n\nclass FrameIterator:\n\tdef __init__(self, driver, start_frame=None):\n\t\tif start_frame is None:\n\t\t\tstart_frame = []\n\t\tself.driver = driver\n\t\tself.start_frame = start_frame\n\tdef __iter__(self):\n\t\tyield []\n\t\tfor new_frame in range(sys.maxsize):\n\t\t\ttry:\n\t\t\t\tself.driver.switch_to.frame(new_frame)\n\t\t\texcept WebDriverException:\n\t\t\t\tbreak\n\t\t\telse:\n\t\t\t\tnew_start_frame = self.start_frame + [new_frame]\n\t\t\t\tfor result in FrameIterator(self.driver, new_start_frame):\n\t\t\t\t\tyield [new_frame] + result\n\t\t\t\ttry:\n\t\t\t\t\tself.switch_to_frame(self.start_frame)\n\t\t\t\texcept NoSuchFrameException:\n\t\t\t\t\traise FramesChangedWhileIterating()\n\tdef switch_to_frame(self, frame_index_path):\n\t\tself.driver.switch_to.default_content()\n\t\tfor frame_index in frame_index_path:\n\t\t\tself.driver.switch_to.frame(frame_index)\n\nclass FramesChangedWhileIterating(Exception):\n\tpass"
  },
  {
    "path": "helium/_impl/util/__init__.py",
    "content": ""
  },
  {
    "path": "helium/_impl/util/dictionary.py",
    "content": "def inverse(dictionary):\n\t\"\"\"\n\t{a: {b}} -> {b: {a}}\n\t\"\"\"\n\tresult = {}\n\tfor key, values in dictionary.items():\n\t\tfor value in values:\n\t\t\tif value not in result:\n\t\t\t\tresult[value] = set()\n\t\t\tresult[value].add(key)\n\treturn result"
  },
  {
    "path": "helium/_impl/util/geom.py",
    "content": "from collections import namedtuple\nfrom math import sqrt\n\nclass Rectangle:\n\tdef __init__(self, left=0, top=0, width=0, height=0):\n\t\tself.left = left\n\t\tself.top = top\n\t\tself.right = left + width\n\t\tself.bottom = top + height\n\t@classmethod\n\tdef from_w_h(cls, width, height):\n\t\treturn cls(0, 0, width, height)\n\t@classmethod\n\tdef from_tuple_l_t_w_h(cls, l_t_w_h=None):\n\t\tif l_t_w_h is None:\n\t\t\tl_t_w_h = (0, 0, 0, 0)\n\t\treturn cls(*l_t_w_h)\n\t@classmethod\n\tdef from_tuple_w_h(cls, w_h):\n\t\treturn cls.from_w_h(*w_h)\n\t@classmethod\n\tdef from_struct_l_t_r_b(cls, struct):\n\t\treturn cls.from_l_t_r_b(\n\t\t\tstruct.left, struct.top, struct.right, struct.bottom\n\t\t)\n\t@classmethod\n\tdef from_l_t_r_b(cls, left, top, right, bottom):\n\t\treturn cls(left, top, right - left, bottom - top)\n\t@property\n\tdef width(self):\n\t\treturn self.right - self.left\n\t@property\n\tdef height(self):\n\t\treturn self.bottom - self.top\n\t@property\n\tdef center(self):\n\t\treturn Point(self.left + self.width / 2, self.top + self.height / 2)\n\t@property\n\tdef east(self):\n\t\treturn self.clip(Point(self.right - 1, self.center.y))\n\t@property\n\tdef west(self):\n\t\treturn Point(self.left, self.center.y)\n\t@property\n\tdef north(self):\n\t\treturn Point(self.center.x, self.top)\n\t@property\n\tdef south(self):\n\t\treturn self.clip(Point(self.center.x, self.bottom - 1))\n\t@property\n\tdef northeast(self):\n\t\treturn Point(self.east.x, self.north.y)\n\t@property\n\tdef southeast(self):\n\t\treturn Point(self.east.x, self.south.y)\n\t@property\n\tdef southwest(self):\n\t\treturn Point(self.west.x, self.south.y)\n\t@property\n\tdef northwest(self):\n\t\treturn Point(self.west.x, self.north.y)\n\t@property\n\tdef area(self):\n\t\tif not self:\n\t\t\treturn 0\n\t\treturn self.width * self.height\n\tdef __contains__(self, point):\n\t\treturn self.left <= point.x < self.right and \\\n\t\t\t   self.top <= point.y < self.bottom\n\tdef translate(self, dx, dy):\n\t\tself.left += dx\n\t\tself.right += dx\n\t\tself.top += dy\n\t\tself.bottom += dy\n\t\treturn self\n\tdef clip(self, point):\n\t\treturn Point(\n\t\t\tmin(max(point[0], self.left), max(self.left, self.right - 1)),\n\t\t\tmin(max(point[1], self.top), max(self.top, self.bottom - 1))\n\t\t)\n\tdef intersect(self, rectangle):\n\t\tleft = max(self.left, rectangle.left)\n\t\ttop = max(self.top, rectangle.top)\n\t\tright = min(self.right, rectangle.right)\n\t\tbottom = min(self.bottom, rectangle.bottom)\n\t\treturn self.from_l_t_r_b(left, top, right, bottom) or Rectangle()\n\tdef intersects(self, rectangle):\n\t\treturn bool(self.intersect(rectangle))\n\tdef as_numpy_slice(self):\n\t\treturn slice(self.top, self.bottom), slice(self.left, self.right)\n\tdef is_to_left_of(self, other):\n\t\tself_starts_to_left_of_other = self.left < other.left\n\t\tself_overlaps_other_top = self.top <= other.top < self.bottom\n\t\tother_overlaps_self_top = other.top <= self.top < other.bottom\n\t\treturn self_starts_to_left_of_other and (\n\t\t\tself_overlaps_other_top or\n\t\t\tother_overlaps_self_top\n\t\t)\n\tdef is_to_right_of(self, other):\n\t\treturn other.is_to_left_of(self)\n\tdef is_above(self, other):\n\t\tself_starts_above_other = self.top < other.top\n\t\tself_overlaps_other_left = self.left <= other.left < self.right\n\t\tother_overlaps_self_left = other.left <= self.left < other.right\n\t\treturn self_starts_above_other and (\n\t\t\tself_overlaps_other_left or\n\t\t\tother_overlaps_self_left\n\t\t)\n\tdef is_below(self, other):\n\t\treturn other.is_above(self)\n\tdef is_in_direction(self, in_direction, of_other):\n\t\treturn getattr(self, 'is_' + in_direction)(of_other)\n\tdef distance_to(self, other):\n\t\tleftmost = self if self.left < other.left else other\n\t\trightmost = self if leftmost == other else other\n\t\tdistance_x = max(0, rightmost.left - leftmost.right)\n\t\ttopmost = self if self.top < other.top else other\n\t\tbottommost = self if topmost == other else other\n\t\tdistance_y = max(0, bottommost.top - topmost.bottom)\n\t\treturn sqrt(distance_x ** 2 + distance_y ** 2)\n\tdef __eq__(self, other):\n\t\tif not isinstance(other, Rectangle):\n\t\t\treturn False\n\t\treturn self.left == other.left and self.top == other.top and \\\n\t\t\t   self.right == other.right and self.bottom == other.bottom\n\tdef __ne__(self, other):\n\t\treturn not self.__eq__(other)\n\tdef __bool__(self):\n\t\treturn bool(self.width > 0 and self.height > 0)\n\tdef __repr__(self):\n\t\treturn type(self).__name__ + '(left=%d, top=%d, width=%d, height=%d)' \\\n\t\t\t   % (self.left, self.top, self.width, self.height)\n\tdef __hash__(self):\n\t\treturn self.left + 7 * self.top + 11 * self.right + 13 * self.bottom\n\nclass Point(namedtuple('Point', ['x', 'y'])):\n\tdef __new__(cls, x=0, y=0):\n\t\treturn cls.__bases__[0].__new__(cls, x, y)\n\tdef __init__(self, x=0, y=0):\n\t\t# tuple is immutable so can't do anything here. The initialization\n\t\t# happens in __new__(...) above.\n\t\tpass\n\t@classmethod\n\tdef from_tuple(cls, tpl):\n\t\treturn cls(*tpl)\n\tdef __eq__(self, other):\n\t\treturn (self.x, self.y) == other\n\tdef __ne__(self, other):\n\t\treturn not self == other\n\tdef __add__(self, other):\n\t\tdx, dy = other\n\t\treturn Point(self.x + dx, self.y + dy)\n\tdef __radd__(self, other):\n\t\treturn self.__add__(other)\n\tdef __sub__(self, other):\n\t\tdx, dy = other\n\t\treturn Point(self.x - dx, self.y - dy)\n\tdef __rsub__(self, other):\n\t\tx, y = other\n\t\tdx, dy = self\n\t\treturn Point(x - dx, y - dy)\n\tdef __mul__(self, scalar):\n\t\tif isinstance(scalar, (int, float)):\n\t\t\treturn Point(self.x * scalar, self.y * scalar)\n\t\telse:\n\t\t\traise ValueError(\"Invalid argument\")\n\tdef __rmul__(self, scalar):\n\t\treturn self.__mul__(scalar)\n\tdef __div__(self, scalar):\n\t\tif isinstance(scalar, (int, float)):\n\t\t\treturn Point(self.x / scalar, self.y / scalar)\n\t\telse:\n\t\t\traise ValueError(\"Invalid argument\")\n\tdef __bool__(self):\n\t\treturn bool(self.x) or bool(self.y)\n\nclass Direction:\n\tdef __init__(self, unit_vector):\n\t\tself.unit_vector = unit_vector\n\tdef iterate_points_starting_at(self, point, offsets):\n\t\tfor offset in offsets:\n\t\t\tyield point + offset * self.unit_vector\n\tdef is_horizontal(self):\n\t\treturn bool(self.unit_vector.x)\n\tdef is_vertical(self):\n\t\treturn not self.is_horizontal()\n\t@property\n\tdef orthog_vector(self):\n\t\treturn Point(-self.unit_vector[1], self.unit_vector[0])\n\tdef __eq__(self, other):\n\t\treturn self.unit_vector == other.unit_vector\n\tdef __repr__(self):\n\t\tfor module_element in dir(self.__module__):\n\t\t\tif self == getattr(self.__module__, module_element):\n\t\t\t\treturn module_element\n\nNORTH = Direction(Point(0, -1))\nEAST = Direction(Point(1, 0))\nSOUTH = Direction(Point(0, 1))\nWEST = Direction(Point(-1, 0))"
  },
  {
    "path": "helium/_impl/util/html.py",
    "content": "from html.parser import HTMLParser\nimport re\n\ndef strip_tags(html):\n\ts = TagStripper()\n\ts.feed(html)\n\treturn s.get_data()\n\nclass TagStripper(HTMLParser):\n\tdef __init__(self):\n\t\tHTMLParser.__init__(self)\n\t\tself.reset()\n\t\tself.fed = []\n\tdef handle_data(self, d):\n\t\tself.fed.append(d)\n\tdef get_data(self):\n\t\treturn ''.join(self.fed)\n\ndef get_easily_readable_snippet(html):\n\thtml = normalize_whitespace(html)\n\ttry:\n\t\tinner_start = html.index('>') + 1\n\t\tinner_end = html.rindex('<', inner_start)\n\texcept ValueError:\n\t\treturn html\n\topening_tag = html[:inner_start]\n\tclosing_tag = html[inner_end:]\n\tinner = html[inner_start:inner_end]\n\tif '<' in inner or len(inner) > 60:\n\t\treturn '%s...%s' % (opening_tag, closing_tag)\n\telse:\n\t\treturn html\n\ndef normalize_whitespace(html):\n\tresult = html.strip()\n\t# Remove multiple spaces:\n\tresult = re.sub(r'\\s+', ' ', result)\n\t# Remove spaces after opening or before closing tags:\n\tresult = result.replace('> ', '>').replace(' <', '<')\n\treturn result"
  },
  {
    "path": "helium/_impl/util/inspect_.py",
    "content": "from helium._impl.util.lang import isbound\nimport inspect\n\ndef repr_args(f, args=None, kwargs=None, repr_fn=repr):\n\tif args is None:\n\t\targs = []\n\tif kwargs is None:\n\t\tkwargs = {}\n\targ_names, _, _, defaults = inspect.getfullargspec(f)[:4]\n\tif isbound(f):\n\t\t# Skip 'self' parameter:\n\t\targ_names = arg_names[1:]\n\tnum_defaults = 0 if defaults is None else len(defaults)\n\tnum_requireds = len(arg_names) - num_defaults\n\tresult = []\n\tfor i, arg_name in enumerate(arg_names):\n\t\thas_default = i >= len(arg_names) - num_defaults\n\t\tif has_default:\n\t\t\tdefault_value = defaults[i - num_requireds]\n\t\tif i < len(args): # Normal arg\n\t\t\tvalue = args[i]\n\t\t\tprefix = ''\n\t\t\tvalue_is_default = has_default and value == default_value\n\t\telif arg_name in kwargs: # Keyword arg\n\t\t\tvalue = kwargs[arg_name]\n\t\t\tprefix = arg_name + '='\n\t\t\tvalue_is_default = has_default and value == default_value\n\t\telse: # Optional arg without given value\n\t\t\tvalue_is_default = True\n\t\tif not value_is_default:\n\t\t\tresult.append(prefix + repr_fn(value))\n\tfor vararg in args[len(arg_names):]:\n\t\tresult.append(repr_fn(vararg))\n\tfor kwarg in kwargs:\n\t\tif kwarg not in arg_names:\n\t\t\tresult.append(kwarg + '=' + repr_fn(kwargs[kwarg]))\n\treturn ', '.join(result)"
  },
  {
    "path": "helium/_impl/util/lang.py",
    "content": "class TemporaryAttrValue:\n\tdef __init__(self, obj, attr, value):\n\t\tself.obj = obj\n\t\tself.attr = attr\n\t\tself.value = value\n\t\tself.value_before = None\n\tdef __enter__(self):\n\t\tself.value_before = getattr(self.obj, self.attr)\n\t\tsetattr(self.obj, self.attr, self.value)\n\tdef __exit__(self, *_):\n\t\tsetattr(self.obj, self.attr, self.value_before)\n\t\tself.value_before = None\n\ndef isbound(method_or_fn):\n\ttry:\n\t\treturn method_or_fn.__self__ is not None\n\texcept AttributeError: # Python 3\n\t\ttry:\n\t\t\treturn method_or_fn.__self__ is not None\n\t\texcept AttributeError:\n\t\t\treturn False"
  },
  {
    "path": "helium/_impl/util/path.py",
    "content": "from errno import EEXIST\nfrom os.path import split, isdir\nfrom os import makedirs\n\ndef get_components(path):\n\tfolders = []\n\twhile True:\n\t\tpath, folder = split(path)\n\t\tif folder != \"\":\n\t\t\tfolders.append(folder)\n\t\telse:\n\t\t\tif path != \"\":\n\t\t\t\tfolders.append(path)\n\t\t\tbreak\n\treturn list(reversed(folders))\n\ndef ensure_exists(path):\n\t\"\"\"http://stackoverflow.com/a/600612/190597 (tzot)\"\"\"\n\ttry:\n\t\tmakedirs(path, exist_ok=True)  # Python>3.2\n\texcept TypeError:\n\t\ttry:\n\t\t\tmakedirs(path)\n\t\texcept OSError as exc: # Python >2.5\n\t\t\tif exc.errno == EEXIST and isdir(path):\n\t\t\t\tpass\n\t\t\telse: raise"
  },
  {
    "path": "helium/_impl/util/system.py",
    "content": "\"\"\"\nGives information about the current operating system.\n\"\"\"\nimport sys\n\ndef is_windows():\n\treturn sys.platform in ('win32', 'cygwin')\n\ndef is_mac():\n\treturn sys.platform == 'darwin'\n\ndef is_linux():\n\treturn sys.platform.startswith('linux')\n\ndef get_canonical_os_name():\n\tif is_windows():\n\t\treturn 'windows'\n\telif is_mac():\n\t\treturn 'mac'\n\telif is_linux():\n\t\treturn 'linux'"
  },
  {
    "path": "helium/_impl/util/xpath.py",
    "content": "# -*- coding: utf-8 -*-\ndef lower(text):\n\talphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝ'\n\treturn \"translate(%s, '%s', '%s')\" % (text, alphabet, alphabet.lower())\n\ndef replace_nbsp(text, by=' '):\n\treturn \"translate(%s, '\\u00a0', %r)\" % (text, by)\n\ndef predicate(condition):\n\treturn '[%s]' % condition if condition else ''\n\ndef predicate_or(*conditions):\n\treturn predicate(' or '.join([c for c in conditions if c]))"
  },
  {
    "path": "requirements/base.txt",
    "content": "# Also update setup.py when you edit this file.\nselenium>=4.29.0"
  },
  {
    "path": "requirements/docs.txt",
    "content": "-r base.txt\n\nsphinx-rtd-theme==3.0.2\nsphinx==8.2.3"
  },
  {
    "path": "requirements/test.txt",
    "content": "-r base.txt\n\nsetuptools<60\npsutil\npywin32; platform_system=='Windows'"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup, find_packages\n\nsetup(\n\tname = 'helium',\n\t# Also update docs/conf.py when you change this:\n\tversion = '7.0.0',\n\tauthor = 'Michael Herrmann',\n\tauthor_email = 'michael+removethisifyouarehuman@herrmann.io',\n\tdescription = 'Lighter browser automation based on Selenium.',\n\tkeywords = 'helium selenium browser automation',\n\turl = 'https://github.com/mherrmann/helium',\n\tpython_requires='>=3',\n\tpackages = find_packages(exclude=['tests', 'tests.*']),\n\tinstall_requires = [\n\t\t# Also update requirements/base.txt when you make changes here.\n\t\t'selenium>=4.16.0'\n\t],\n\tpackage_data = {\n\t\t'helium._impl': ['webdrivers/**/*']\n\t},\n\tzip_safe = False,\n\tclassifiers=[\n\t\t'Development Status :: 5 - Production/Stable',\n\t\t'Intended Audience :: Developers',\n\t\t'License :: OSI Approved :: MIT License',\n\t\t'Topic :: Software Development :: Testing',\n\t\t'Topic :: Software Development :: Libraries',\n\t\t'Programming Language :: Python',\n\t\t'Programming Language :: Python :: 3.5',\n\t\t'Programming Language :: Python :: 3.6',\n\t\t'Programming Language :: Python :: 3.7',\n\t\t'Programming Language :: Python :: 3.8',\n\t\t'Programming Language :: Python :: 3.9',\n\t\t'Programming Language :: Python :: 3.10',\n\t\t'Programming Language :: Python :: 3.11',\n\t\t'Operating System :: Microsoft :: Windows',\n\t\t'Operating System :: POSIX :: Linux',\n\t\t'Operating System :: MacOS :: MacOS X'\n\t],\n\ttest_suite='tests'\n)"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/api/__init__.py",
    "content": "from helium import start_chrome, start_firefox, go_to, set_driver, \\\n\tkill_browser\nfrom selenium.webdriver import ChromeOptions\nfrom selenium.webdriver.common.by import By\nfrom tests.api.util import get_data_file_url\nfrom time import time, sleep\nfrom unittest import TestCase\n\nimport os\n\ndef test_browser_name():\n\ttry:\n\t\tbrowser_name = os.environ['TEST_BROWSER']\n\texcept KeyError:\n\t\treturn 'chrome'\n\telse:\n\t\treturn browser_name\n\nclass BrowserAT(TestCase):\n\t@classmethod\n\tdef setUpClass(cls):\n\t\tif _TEST_BROWSER is None:\n\t\t\tcls.driver = start_browser()\n\t\t\tcls.started_browser = True\n\t\telse:\n\t\t\tcls.driver = _TEST_BROWSER\n\t\t\tcls.started_browser = False\n\t\tset_driver(cls.driver)\n\tdef setUp(self):\n\t\tgo_to(self.get_url())\n\tdef get_url(self):\n\t\treturn get_data_file_url(self.get_page())\n\tdef get_page(self):\n\t\traise NotImplementedError()\n\tdef read_result_from_browser(self, timeout_secs=3):\n\t\tstart_time = time()\n\t\twhile time() < start_time + timeout_secs:\n\t\t\tresult = self.driver\\\n\t\t\t\t.find_element(By.ID, 'result').get_attribute('innerHTML')\n\t\t\tif result:\n\t\t\t\treturn result\n\t\t\tsleep(0.2)\n\t\treturn ''\n\tdef assertFindsEltWithId(self, predicate, id_):\n\t\tself.assertEqual(id_, predicate.web_element.get_attribute('id'))\n\t@classmethod\n\tdef tearDownClass(cls):\n\t\tif cls.started_browser:\n\t\t\tkill_browser()\n\n_TEST_BROWSER = None\n\ndef setUpModule():\n\tglobal _TEST_BROWSER\n\t_TEST_BROWSER = start_browser()\n\ndef tearDownModule():\n\tglobal _TEST_BROWSER\n\tif _TEST_BROWSER is not None:\n\t\tkill_browser()\n\t_TEST_BROWSER = None\n\ndef start_browser(url=None):\n\tbrowser_name = test_browser_name()\n\tkwargs = {}\n\tif browser_name in ('chrome', 'firefox'):\n\t\tkwargs['headless'] = True\n\tif browser_name == 'chrome':\n\t\toptions = ChromeOptions()\n\t\t# Fix the locale for inputting dates and times:\n\t\toptions.add_argument('lang=de-DE')\n\t\tkwargs['options'] = options\n\treturn _TEST_BROWSERS[browser_name](url, **kwargs)\n\n_TEST_BROWSERS = {\n\t'firefox': start_firefox,\n\t'chrome': start_chrome\n}"
  },
  {
    "path": "tests/api/data/default.css",
    "content": "#result {\n    clear: both;\n}"
  },
  {
    "path": "tests/api/data/js/jquery.ui-contextmenu.js",
    "content": "/*******************************************************************************\n * jquery.ui-contextmenu.js plugin.\n *\n * jQuery plugin that provides a context menu (based on the jQueryUI menu widget).\n *\n * @see https://github.com/mar10/jquery-ui-contextmenu\n *\n * Copyright (c) 2013, Martin Wendt (http://wwWendt.de). Licensed MIT.\n */\n;(function($, window, document, undefined) {\n\t\"use strict\";\n\tvar supportSelectstart = \"onselectstart\" in document.createElement(\"div\");\n\n\t/** Return command without leading '#' (default to \"\"). */\n\tfunction normCommand(cmd){\n\t\treturn (cmd && cmd.match(/^#/)) ? cmd.substring(1) : (cmd || \"\");\n\t}\n\n\n\t$.widget(\"moogle.contextmenu\", {\n\t\tversion: \"1.2.2\",\n\t\toptions: {\n\t\t\tdelegate: null,       // selector\n\t\t\thide: { effect: \"fadeOut\", duration: \"fast\"},\n\t\t\tignoreParentSelect: true, // Don't trigger 'select' for sub-menu parents\n\t\t\tmenu: null,           // selector or jQuery pointing to <UL>, or a definition hash\n\t\t\tposition: null,       // popup positon\n\t\t\tpreventSelect: false, // disable text selection of target\n\t\t\tshow: { effect: \"slideDown\", duration: \"fast\"},\n\t\t\ttaphold: false,       // open menu on taphold events (requires external plugins)\n\t\t\t// Events:\n\t\t\tbeforeOpen: $.noop,   // menu about to open; return `false` to prevent opening\n\t\t\tblur: $.noop,         // menu option lost focus\n\t\t\tclose: $.noop,        // menu was closed\n\t\t\tcreate: $.noop,       // menu was initialized\n\t\t\tcreateMenu: $.noop,   // menu was initialized (original UI Menu)\n\t\t\tfocus: $.noop,        // menu option got focus\n\t\t\topen: $.noop,         // menu was opened\n\t\t\tselect: $.noop        // menu option was selected; return `false` to prevent closing\n\t\t},\n\t\t/** Constructor */\n\t\t_create: function () {\n\t\t\tvar eventNames, targetId,\n\t\t\t\topts = this.options;\n\n\t\t\tthis.$headStyle = null;\n\t\t\tthis.$menu = null;\n\t\t\tthis.menuIsTemp = false;\n\t\t\tthis.currentTarget = null;\n\n\t\t\tif(opts.preventSelect){\n\t\t\t\t// Create a global style for all potential menu targets\n\t\t\t\t// If the contextmenu was bound to `document`, we apply the\n\t\t\t\t// selector relative to the <body> tag instead\n\t\t\t\ttargetId = ($(this.element).is(document) ? $(\"body\") : this.element).uniqueId().attr(\"id\");\n\t\t\t\tthis.$headStyle = $(\"<style class='moogle-contextmenu-style'>\")\n\t\t\t\t\t.prop(\"type\", \"text/css\")\n\t\t\t\t\t.html(\"#\" + targetId + \" \" + opts.delegate + \" { \" +\n\t\t\t\t\t\t\"-webkit-user-select: none; \" +\n\t\t\t\t\t\t\"-khtml-user-select: none; \" +\n\t\t\t\t\t\t\"-moz-user-select: none; \" +\n\t\t\t\t\t\t\"-ms-user-select: none; \" +\n\t\t\t\t\t\t\"user-select: none; \" +\n\t\t\t\t\t\t\"}\")\n\t\t\t\t\t.appendTo(\"head\");\n\t\t\t\t// TODO: the selectstart is not supported by FF?\n\t\t\t\tif(supportSelectstart){\n\t\t\t\t\tthis.element.delegate(opts.delegate, \"selectstart\" + this.eventNamespace, function(event){\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._createUiMenu(opts.menu);\n\n\t\t\teventNames = \"contextmenu\" + this.eventNamespace;\n\t\t\tif(opts.taphold){\n\t\t\t\teventNames += \" taphold\" + this.eventNamespace;\n\t\t\t}\n\t\t\tthis.element.delegate(opts.delegate, eventNames, $.proxy(this._openMenu, this));\n\t\t},\n\t\t/** Destructor, called on $().contextmenu(\"destroy\"). */\n\t\t_destroy: function(){\n\t\t\tthis.element.undelegate(this.eventNamespace);\n\n\t\t\tthis._createUiMenu(null);\n\n\t\t\tif(this.$headStyle){\n\t\t\t\tthis.$headStyle.remove();\n\t\t\t\tthis.$headStyle = null;\n\t\t\t}\n\t\t},\n\t\t/** (Re)Create jQuery UI Menu. */\n\t\t_createUiMenu: function(menuDef){\n\t\t\t// Remove temporary <ul> if any\n\t\t\tif(this.isOpen()){\n\t\t\t\t// close without animation, to force async mode\n\t\t\t\tthis._closeMenu(true);\n\t\t\t}\n\n\t\t\tif(this.menuIsTemp){\n\t\t\t\tthis.$menu.remove(); // this will also destroy ui.menu\n\t\t\t} else if(this.$menu){\n\t\t\t\tthis.$menu.menu(\"destroy\").hide();\n\t\t\t}\n\t\t\tthis.$menu = null;\n\t\t\tthis.menuIsTemp = false;\n\t\t\t// If a menu definition array was passed, create a hidden <ul>\n\t\t\t// and generate the structure now\n\t\t\tif( ! menuDef ){\n\t\t\t\treturn;\n\t\t\t} else if($.isArray(menuDef)){\n\t\t\t\tthis.$menu = $.moogle.contextmenu.createMenuMarkup(menuDef);\n\t\t\t\tthis.menuIsTemp = true;\n\t\t\t}else if ( typeof menuDef === \"string\" ){\n\t\t\t\tthis.$menu = $(menuDef);\n\t\t\t}else{\n\t\t\t\tthis.$menu = menuDef;\n\t\t\t}\n\t\t\t// Create - but hide - the jQuery UI Menu widget\n\t\t\tthis.$menu\n\t\t\t\t.hide()\n//\t\t\t\t.addClass(\"moogle-contextmenu\")\n\t\t\t\t// Create a menu instance that delegates events to our widget\n\t\t\t\t.menu({\n\t\t\t\t\tblur: $.proxy(this.options.blur, this),\n\t\t\t\t\tcreate: $.proxy(this.options.createMenu, this),\n\t\t\t\t\tfocus: $.proxy(this.options.focus, this),\n\t\t\t\t\tselect: $.proxy(function(event, ui){\n\t\t\t\t\t\t// User selected a menu entry\n\t\t\t\t\t\tvar retval,\n\t\t\t\t\t\t\tisParent = (ui.item.has(\">a[aria-haspopup='true']\").length > 0),\n\t\t\t\t\t\t\t$a = ui.item.find(\">a\"),\n\t\t\t\t\t\t\tactionHandler = $a.data(\"actionHandler\");\n\t\t\t\t\t\tui.cmd = normCommand($a.attr(\"href\"));\n\t\t\t\t\t\tui.target = $(this.currentTarget);\n\t\t\t\t\t\t// ignore clicks, if they only open a sub-menu\n\t\t\t\t\t\tif( !isParent || !this.options.ignoreParentSelect){\n\t\t\t\t\t\t\tretval = this._trigger.call(this, \"select\", event, ui);\n\t\t\t\t\t\t\tif( actionHandler ){\n\t\t\t\t\t\t\t\tretval = actionHandler.call(this, event, ui);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif( retval !== false ){\n\t\t\t\t\t\t\t\tthis._closeMenu.call(this);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, this)\n\t\t\t\t});\n\t\t},\n\t\t/** Open popup (called on 'contextmenu' event). */\n\t\t_openMenu: function(event){\n\t\t\tvar opts = this.options,\n\t\t\t\tposOption = opts.position,\n\t\t\t\tself = this,\n\t\t\t\tui = {menu: this.$menu, target: $(event.target)};\n\t\t\tthis.currentTarget = event.target;\n\t\t\t// Prevent browser from opening the system context menu\n\t\t\tevent.preventDefault();\n\n\t\t\tif( this._trigger(\"beforeOpen\", event, ui) === false ){\n\t\t\t\tthis.currentTarget = null;\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tui.menu = this.$menu; // Might have changed in beforeOpen\n\t\t\t// Register global event handlers that close the dropdown-menu\n\t\t\t$(document).bind(\"keydown\" + this.eventNamespace, function(event){\n\t\t\t\tif( event.which === $.ui.keyCode.ESCAPE ){\n\t\t\t\t\tself._closeMenu();\n\t\t\t\t}\n\t\t\t}).bind(\"mousedown\" + this.eventNamespace + \" touchstart\" + this.eventNamespace, function(event){\n\t\t\t\t// Close menu when clicked outside menu\n\t\t\t\tif( !$(event.target).closest(\".ui-menu-item\").length ){\n\t\t\t\t\tself._closeMenu();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// required for custom positioning (issue #18 and #13).\n\t\t\tif ($.isFunction(posOption)) {\n\t\t\t\tposOption = posOption(event, ui);\n\t\t\t}\n\t\t\tposOption = $.extend({\n\t\t\t\tmy: \"left top\",\n\t\t\t\tat: \"left bottom\",\n\t\t\t\t// if called by 'open' method, event does not have pageX/Y\n\t\t\t\tof: (event.pageX === undefined) ? event.target : event,\n\t\t\t\tcollision: \"fit\"\n\t\t\t}, posOption);\n\n\t\t\t// Finally display the popup\n\t\t\tthis.$menu\n\t\t\t\t.show() // required to fix positioning error\n\t\t\t\t.css({\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tleft: 0,\n\t\t\t\t\ttop: 0\n\t\t\t\t}).position(posOption)\n\t\t\t\t.hide(); // hide again, so we can apply nice effects\n\n\t\t\tthis._show(this.$menu, this.options.show, function(){\n\t\t\t\tself._trigger.call(self, \"open\", event, ui);\n\t\t\t});\n\t\t},\n\t\t/** Close popup. */\n\t\t_closeMenu: function(immediately){\n\t\t\tvar self = this,\n\t\t\t\thideOpts = immediately ? false : this.options.hide;\n\n\t\t\t// Note: we don't want to unbind the 'contextmenu' event\n\t\t\t$(document)\n\t\t\t\t.unbind(\"mousedown\" + this.eventNamespace)\n\t\t\t\t.unbind(\"touchstart\" + this.eventNamespace)\n\t\t\t\t.unbind(\"keydown\" + this.eventNamespace);\n\n\t\t\tthis._hide(this.$menu, hideOpts, function() {\n\t\t\t\tself._trigger(\"close\");\n\t\t\t\tself.currentTarget = null;\n\t\t\t});\n\t\t},\n\t\t/** Handle $().contextmenu(\"option\", key, value) calls. */\n\t\t_setOption: function(key, value){\n\t\t\tswitch(key){\n\t\t\tcase \"menu\":\n\t\t\t\tthis.replaceMenu(value);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t$.Widget.prototype._setOption.apply(this, arguments);\n\t\t},\n\t\t/** Return ui-menu entry (<A> or <LI> tag). */\n\t\t_getMenuEntry: function(cmd, wantLi){\n\t\t\tvar $entry = this.$menu.find(\"li a[href=#\" + normCommand(cmd) + \"]\");\n\t\t\treturn wantLi ? $entry.closest(\"li\") : $entry;\n\t\t},\n\t\t/** Close context menu. */\n\t\tclose: function(){\n\t\t\tif(this.isOpen()){\n\t\t\t\tthis._closeMenu();\n\t\t\t}\n\t\t},\n\t\t/** Enable or disable the menu command. */\n\t\tenableEntry: function(cmd, flag){\n\t\t\tthis._getMenuEntry(cmd, true).toggleClass(\"ui-state-disabled\", (flag === false));\n\t\t},\n\t\t/** Redefine the whole menu. */\n\t\t/** Return Menu element (UL). */\n\t\tgetMenu: function(){\n\t\t\treturn this.$menu;\n\t\t},\n\t\t/** Return true if menu is open. */\n\t\tisOpen: function(){\n//            return this.$menu && this.$menu.is(\":visible\");\n\t\t\treturn !!this.$menu && !!this.currentTarget;\n\t\t},\n\t\t/** Open context menu on a specific target (must match options.delegate) */\n\t\topen: function(target){\n\t\t\t// Fake a 'contextmenu' event\n\t\t\tvar e = jQuery.Event(\"contextmenu\", {target: target.get(0)});\n\t\t\treturn this.element.trigger(e);\n\t\t},\n\t\treplaceMenu: function(data){\n\t\t\tthis._createUiMenu(data);\n\t\t},\n\t\t/** Redefine menu entry (title or all of it). */\n\t\tsetEntry: function(cmd, titleOrData){\n\t\t\tvar $parent,\n\t\t\t\t$entry = this._getMenuEntry(cmd, false);\n\n\t\t\tif(typeof titleOrData === \"string\"){\n\t\t\t\t// Replace <a> text without removing <span> child\n\t\t\t\t$entry\n\t\t\t\t\t.contents()\n\t\t\t\t\t.filter(function(){ return this.nodeType === 3; })\n\t\t\t\t\t.first()\n\t\t\t\t\t.replaceWith(titleOrData);\n\t\t\t}else{\n\t\t\t\t$parent = $entry.closest(\"li\").empty();\n\t\t\t\t$.moogle.contextmenu.createEntryMarkup(titleOrData, $parent);\n\t\t\t}\n\t\t},\n\t\t/** Show or hide the menu command. */\n\t\tshowEntry: function(cmd, flag){\n\t\t\tthis._getMenuEntry(cmd, true).toggle(flag !== false);\n\t\t}\n\t});\n\n/*\n * Global functions\n */\n$.extend($.moogle.contextmenu, {\n\t/** Convert a menu description into a into a <li> content. */\n\tcreateEntryMarkup: function(entry, $parentLi){\n\t\tvar $a = null;\n\n\t\tif(entry.title.match(/^---/)){\n\t\t\t$parentLi.text(entry.title);\n\t\t}else{\n\t\t\t$a = $(\"<a>\", {\n\t\t\t\ttext: \"\" + entry.title,\n\t\t\t\thref: \"#\" + normCommand(entry.cmd)\n\t\t\t}).appendTo($parentLi);\n\t\t\tif( $.isFunction(entry.action) ){\n\t\t\t\t$a.data(\"actionHandler\", entry.action);\n\t\t\t}\n\t\t\tif(entry.uiIcon){\n\t\t\t\t$a.append($(\"<span class='ui-icon'>\").addClass(entry.uiIcon));\n\t\t\t}\n\t\t\tif(entry.disabled){\n\t\t\t\t$parentLi.addClass(\"ui-state-disabled\");\n\t\t\t}\n\t\t\tif($.isPlainObject(entry.data)){\n\t\t\t\t$a.data(entry.data);\n\t\t\t}\n\t\t}\n\t\treturn $a;\n\t},\n\t/** Convert a nested array of command objects into a <ul> structure. */\n\tcreateMenuMarkup: function(options, $parentUl){\n\t\tvar i, menu, $ul, $li;\n\t\tif( $parentUl == null ){\n\t\t\t$parentUl = $(\"<ul class='ui-helper-hidden'>\").appendTo(\"body\");\n\t\t}\n\t\tfor(i = 0; i < options.length; i++){\n\t\t\tmenu = options[i];\n\t\t\t$li = $(\"<li>\").appendTo($parentUl);\n\n\t\t\t$.moogle.contextmenu.createEntryMarkup(menu, $li);\n\n\t\t\tif( $.isArray(menu.children) ){\n\t\t\t\t$ul = $(\"<ul>\").appendTo($li);\n\t\t\t\t$.moogle.contextmenu.createMenuMarkup(menu.children, $ul);\n\t\t\t}\n\t\t}\n\t\treturn $parentUl;\n\t}\n});\n\n}(jQuery, window, document));\n"
  },
  {
    "path": "tests/api/data/js/util.js",
    "content": "function setResult(result) {\n    document.getElementById('result').innerHTML = result;\n}"
  },
  {
    "path": "tests/api/data/test_alert.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_alert</title>\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n\t<script type=\"text/javascript\">\n\t\tfunction promptAndSetResult() {\n\t\t\tvar promptResult = prompt('Please enter a value', '');\n\t\t\tif (promptResult == null || typeof promptResult == 'undefined') {\n\t\t\t\tpromptResult = '';\n\t\t\t}\n\t\t\tsetResult('Value entered: ' + promptResult);\n\t\t}\n\t</script>\n</head>\n<body>\n\t<a href=\"#\" onclick=\"alert('Hello World!'); setResult('Alert displayed');\">\n\t\tDisplay alert\n\t</a>\n\t<br/>\n\t<a href=\"#\"\n\t   onclick=\"setResult(confirm('Proceed?') ? 'Accepted' : 'Dismissed');\">\n\t\tAsk for confirmation\n\t</a>\n\t<br/>\n\t<a href=\"#\" onclick=\"promptAndSetResult();\">Prompt for value</a>\n\t<p id=\"result\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_aria.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t\t<title>Test page for browser system tests</title>\n        <style>\n\n        </style>\n\t</head>\n<body>\n\t<form>\n\t\t<button aria-label=\"Close\">X</button>\n\t</form>\n\t<form>\n\t\t<button aria-label=\"Disabled Close\" aria-disabled=\"true\">X</button>\n\t</form>\n\t<div role=\"button\" aria-label=\"Attach files\" aria-disabled=\"false\" style=\"display: block; width: 20px; height: 20px; background-color: green;\"></div>\n\t<div role=\"button\" aria-label=\"Disabled Attach files\" aria-disabled=\"true\" style=\"display: block; width: 20px; height: 20px; background-color: red;\"></div>\n\t<form>\n\t\t<input type=\"submit\" value=\"\" aria-label=\"Submit\" />\n\t</form>\n\t<table>\n\t\t<tr>\n\t\t\t<td><div id=\"TextboxLabel\">Textbox:</div></td>\n\t\t\t<td>\n\t\t\t\t<div role=\"textbox\" aria-labelledby=\"TextboxLabel\">\n\t\t\t\t\tTextbox value\n\t\t\t\t</div>\n\t\t\t</td>\n\t\t</tr>\n\t</table>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_click.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_click</title>\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n\t<style type=\"text/css\">\n\t\t/* Taken from twitter.com: */\n\t\t.visuallyhidden {\n\t\t\tborder: 0;\n\t\t\tclip: rect(0 0 0 0);\n\t\t\theight: 1px;\n\t\t\tmargin: -1px;\n\t\t\toverflow: hidden;\n\t\t\tpadding: 0;\n\t\t\tposition: absolute;\n\t\t\twidth: 1px;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<!--Having this hidden span here lets us test whether Helium is clever-->\n\t<!--enough to give preference to buttons over spans when clicking. The-->\n\t<!--example is inspired by Twitter's \"Tweet\" button.-->\n\t<span class=\"visuallyhidden\">Click me!</span>\n\t<button onclick=\"setResult('Success!');\">Click me!</button>\n\t<p id=\"result\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_doubleclick.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_doubleclick</title>\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n</head>\n<body>\n\t<p ondblclick=\"setResult('Success!');\">Doubleclick here.</p>\n\t<p id=\"result\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_drag/default.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<script type=\"text/javascript\" src=\"../js/util.js\"></script>\n\t<script type=\"text/javascript\" src=\"../js/jquery.min.js\"></script>\n\t<script type=\"text/javascript\" src=\"../js/jquery-ui.min.js\"></script>\n\t<script type=\"text/javascript\">\n\t\t$(document).ready(function() {\n\t\t\t$('#draggable').draggable();\n\t\t\t$('.dropTarget').droppable({\n\t\t\t\tdrop: function(ev, ui){\n\t\t\t\t\tif (ev.target.id == 'target') {\n\t\t\t\t\t\tsetResult('Success!');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t</script>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"../default.css\">\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"test_drag.css\">\n</head>\n<body>\n\t<div class=\"dropTarget\">\n\t\t<p id=\"draggable\">Drag me.</p>\n\t</div>\n\t<div id=\"target\" class=\"dropTarget\"></div>\n\t<p id=\"result\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_drag/html5.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<script type=\"text/javascript\" src=\"../js/util.js\"></script>\n\t<script type=\"text/javascript\">\n\t\tfunction allowDrop(ev) {\n\t\t\tev.preventDefault();\n\t\t}\n\t\tfunction drag(ev) {\n\t\t\tev.dataTransfer.setData(\"Text\", ev.target.id);\n\t\t}\n\t\tfunction drop(ev) {\n\t\t\tev.preventDefault();\n\t\t\tvar data = ev.dataTransfer.getData(\"Text\");\n\t\t\tev.target.appendChild(document.getElementById(data));\n\t\t\tif (ev.target.id == 'target') {\n\t\t\t\tsetResult('Success!');\n\t\t\t}\n\t\t}\n\t</script>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"../default.css\">\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"test_drag.css\">\n</head>\n<body>\n\t<div class=\"dropTarget\" ondrop=\"drop(event)\" ondragover=\"allowDrop(event)\">\n\t\t<p id=\"draggable\" draggable=\"true\" ondragstart=\"drag(event)\">\n\t\t\tDrag me.\n\t\t</p>\n\t</div>\n\t<div id=\"target\"\n\t\t class=\"dropTarget\" ondrop=\"drop(event)\" ondragover=\"allowDrop(event)\">\n\t</div>\n\t<p id=\"result\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_drag/test_drag.css",
    "content": ".dropTarget {\n    float: left;\n    width: 100px;\n    height: 35px;\n    margin: 10px;\n    padding: 10px;\n    border: 1px solid #aaaaaa;\n    text-align: center;\n}\n\n.dropTarget p {\n    margin-top: 10px;\n}"
  },
  {
    "path": "tests/api/data/test_file_upload/test_file_upload.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_file_upload</title>\n\t<script type=\"text/javascript\" src=\"../js/util.js\"></script>\n\t<script type=\"text/javascript\" src=\"../js/jquery.min.js\"></script>\n\t<script type=\"text/javascript\" src=\"../js/jquery-ui.min.js\"></script>\n\t<script type=\"text/javascript\">\n\t\tfunction isExpectedFile(file) {\n\t\t\treturn file.name == 'upload_this.png' &&\n\t\t\t\t\tfile.size == 5417 && file.type == 'image/png';\n\t\t}\n\t\tfunction onNormalFileUploadChange(event) {\n\t\t\tvar file = event.currentTarget.files[0];\n\t\t\tif (isExpectedFile(file)) {\n\t\t\t\tsetResult(\"Success!\");\n\t\t\t}\n\t\t}\n\t\tfunction enableDropEvent(onElement) {\n\t\t\t// If we don't 'preventDefault' on the events 'dragover' and\n\t\t\t// 'dragenter', the 'drop' event never reaches us. This function\n\t\t\t// ensures that the 'drop' event does get passed through. For a\n\t\t\t// reference, see http://www.quirksmode.org/blog/archives/2009/09/\n\t\t\t// the_html5_drag.html\n\t\t\tvar preventDefault = function(event) {\n\t\t\t\tevent.preventDefault();\n\t\t\t};\n\t\t\tonElement.on('dragover', preventDefault);\n\t\t\tonElement.on('dragenter', preventDefault);\n\t\t}\n\t\t/**\n\t\t * The HTML5 spec at http://www.w3.org/html/wg/drafts/html/master/\n\t\t * editing.html#current-drag-operation says that a drag consists of one\n\t\t * dragenter event followed by a dragover event every 350+-200ms.\n\t\t * The code below tests that the dragover event really is broadcast\n\t\t * continuously by only considering dragover events 1000ms after the\n\t\t * first dragenter event.\n\t\t */\n\t\tvar dragenterTime;\n\t\t$(document).on('dragenter', function(event) {\n\t\t\tdragenterTime = new Date().getTime();\n\t\t\t$(\"#dropFileUploadLabel\").html('Now hold for 1 second...');\n\t\t});\n\t\t$(document).on('dragover', function(event) {\n\t\t\tvar currTime = new Date().getTime();\n\t\t\tif (dragenterTime != undefined && currTime - dragenterTime > 1000) {\n\t\t\t\tvar dropFileUpload = $('#dropFileUpload');\n\t\t\t\tdropFileUpload.addClass('active');\n\t\t\t\tenableDropEvent(dropFileUpload);\n\t\t\t\tdropFileUpload.on('drop', function(event) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tvar file = event.originalEvent.dataTransfer.files[0];\n\t\t\t\t\tif (isExpectedFile(file)) {\n\t\t\t\t\t\tsetResult('Success!');\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t</script>\n\t<style type=\"text/css\">\n\t\t#dropFileUpload {\n\t\t\tborder: 1px solid #aaaaaa;\n\t\t\tpadding: 3px 10px;\n\t\t\tdisplay: inline;\n\t\t\tvisibility: hidden;\n\t\t}\n\t\t#dropFileUpload.active {\n\t\t\tvisibility: visible;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<label for=\"normalFileUpload\">Normal file upload:</label>\n\t<input type=\"file\" id=\"normalFileUpload\"\n\t\t   onchange=\"onNormalFileUploadChange(event)\"/>\n\t<br/>\n\t<label for=\"dropFileUpload\" id=\"dropFileUploadLabel\">\n\t\tDrag a file over this browser window!\n\t</label>\n\t<div id=\"dropFileUpload\">Drop the file here!</div>\n\t<p id=\"result\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_gui_elements.html",
    "content": "<html>\n\t<head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t\t<title>Test page for browser system tests</title>\n        <style>\n            button {\n                width: 100%;\n            }\n            ul.horizontal {\n                list-style-type: none;\n                padding: 0;\n            }\n            ul.horizontal li {\n                display: inline;\n                background-color: lightblue;\n                padding: 0 5px;\n            }\n            tbody {\n                font-size: 11pt;\n            }\n            td.left {\n                text-align: right;\n            }\n            [contenteditable] {\n                border-style: solid;\n                border-width: 1px;\n            }\n            iframe {\n                height: 28px;\n                width: 100%;\n                border: none;\n            }\n        </style>\n\t</head>\n\t<body>\n\t\t<form>\n\t\t\t<table style=\"width: 700px;\">\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Empty Text Field:</td>\n\t\t\t\t\t<td><input type=\"text\" /></td>\n\t\t\t\t\t<td>Another Text Field:</td>\n\t\t\t\t\t<td><input type=\"text\" /></td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Example Text Field:</td>\n\t\t\t\t\t<td>\n\t\t\t\t\t\t<input type=\"text\" value=\"Lorem ipsum\"\n\t\t\t\t\t\t\t   id=\"exampleTextFieldId\"\n\t\t\t\t\t\t\t   name=\"exampleTextFieldName\"\n\t\t\t\t\t\t\t   class=\"exampleTextFieldClass\"/>\n\t\t\t\t\t</td>\n                    <td>CheckBox</td>\n                    <td>\n\t\t\t\t\t\t<input type=\"checkbox\" id=\"checkBoxId\"\n\t\t\t\t\t\t\t   name=\"checkBoxName\" class=\"checkBoxClass\"/>\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Disabled Text Field:</td>\n\t\t\t\t\t<td>\n                        <input type=\"text\" value=\"This is disabled\" disabled />\n                    </td>\n                    <td><input type=\"checkbox\"/> LHS CheckBox</td>\n                </tr>\n                <tr>\n\t\t\t\t\t<td>ReadOnly Text Field:</td>\n\t\t\t\t\t<td>\n                        <input type=\"text\" value=\"This is read only\" readonly />\n                    </td>\n\t\t\t\t\t<td>Language:</td>\n\t\t\t\t\t<td><input type=\"text\" value=\"English\" /></td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Język polski:</td>\n                    <td>\n                        <input type=\"text\"\n                               value=\"Pchnąć w tę łódź jeża lub ośm skrzyń fig\"\n                               readonly />\n                    </td>\n                    <td>Language:</td>\n                    <td>\n                        <select>\n                            <option>English</option>\n                            <option>Polski</option>\n                            <option>Deutsch</option>\n                        </select>\n                    </td>\t\t\t\t\t\n\t\t\t\t</tr>\n                <tr>\n                    <td>Deutsch:</td>\n                    <td>\n                        <input type=\"text\" value=\"Heizölrückstoßabdämpfung\"/>\n                    </td>\n                    <td colspan=\"2\">\n                        <table style=\"width: 100%;\">\n                            <tr>\n                                <td style=\"width: 20%\">&nbsp;</td>\n                                <td style=\"width: 40%; text-align: center;\">\n                                    Column 1\n                                </td>\n                                <td style=\"width: 40%; text-align: center;\">\n                                    Column 2\n                                </td>\n                            </tr>\n                        </table>\n                    </td>\n                </tr>\n                <tr>\n                    <td>Drop Down List:</td>\n                    <td>\n                        <select id=\"dropDownListId\" name=\"dropDownListName\" class=\"dropDownListClass\">\n                            <option>Option One</option>\n                            <option>Option Two</option>\n                            <option>Option Three</option>\n                        </select>\n                    </td>\n\t\t\t\t\t<td colspan=\"2\">\n\t\t\t\t\t\t<table style=\"width: 100%;\">\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td style=\"width: 20%\">Row 1:</td>\n\t\t\t\t\t\t\t\t<td style=\"width: 40%; text-align: center;\">\n\t\t\t\t\t\t\t\t\t<button type=\"button\">\n\t\t\t\t\t\t\t\t\t\tDuplicate Button\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t<td style=\"width: 40%; text-align: center;\">\n\t\t\t\t\t\t\t\t\t<button type=\"button\">\n\t\t\t\t\t\t\t\t\t\tDuplicate Button\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Editable ComboBox:</td>\n\t\t\t\t\t<td>\n\t\t\t\t\t\t<input list=\"numbers\">\n\t\t\t\t\t\t<datalist id=\"numbers\">\n\t\t\t\t\t\t\t<option value=\"One\"></option>\n\t\t\t\t\t\t\t<option value=\"Two\"></option>\n\t\t\t\t\t\t\t<option value=\"Three\"></option>\n\t\t\t\t\t\t</datalist>\n\t\t\t\t\t</td>\n\t\t\t\t\t<td colspan=\"2\">\n\t\t\t\t\t\t<table style=\"width: 100%;\">\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td style=\"width: 20%\">Row 2:</td>\n\t\t\t\t\t\t\t\t<td style=\"width: 40%; text-align: center;\">\n\t\t\t\t\t\t\t\t\t<button type=\"button\">\n\t\t\t\t\t\t\t\t\t\tDuplicate Button\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t<td style=\"width: 40%; text-align: center;\">\n\t\t\t\t\t\t\t\t\t<button type=\"button\">\n\t\t\t\t\t\t\t\t\t\tDuplicate Button\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n                <tr>\n                    <td><input type=\"checkbox\"/>Right Labeled CheckBox</td>\n                    <td class=\"left\">\n                        Left Labeled CheckBox\n                        <input type=\"checkbox\"/>\n                    </td>\n                    <td>\n                        <button type=\"button\">Enabled Button</button>\n                    </td>\n                    <td>\n                        <button type=\"button\" disabled >\n                            Disabled Button\n                        </button>\n                    </td>\n                </tr>\n                <tr>\n                    <td>\n                        <input type=\"checkbox\" checked />\n                        Ticked CheckBox\n                    </td>\n                    <td class=\"left\">\n                        <input type=\"checkbox\" disabled />\n                        Disabled CheckBox\n                    </td>\n                    <td colspan=\"2\" style=\"padding-left: 8px;\">\n\t\t\t\t\t\t<label>\n\t\t\t\t\t\t<!--By setting margin: 0; here, we ensure that the-->\n\t\t\t\t\t\t<!--check box has the same position as the enclosing-->\n\t\t\t\t\t\t<!--label. This is important for the test.-->\n\t\t\t\t\t\t<input type=\"checkbox\" style=\"margin: 0;\"\n\t\t\t\t\t\t\t   id=\"checkBoxEnclosedByLabel\">\n\t\t\t\t\t\t<span>CheckBox enclosed by label</span>\n\t\t\t\t\t\t</label>\n                    </td>\n                </tr>\n                <tr>\n                    <td>\n                        <input type=\"radio\" checked id=\"radioButton1id\" name=\"radioGroup1\" class=\"radioButton1class\"/>\n                        RadioButton 1\n                    </td>\n                    <td class=\"left\">\n                        <input type=\"radio\" name=\"radioGroup2\"/>\n                        Left Labeled RadioButton 1\n                    </td>\n                    <td>\n                        <span id=\"textWithId\"\n\t\t\t\t\t\t\t  name=\"textWithName\"\n\t\t\t\t\t\t\t  class=\"textWithClass\">\n                            Text with id\n                        </span>\n                    </td>\n                    <td>\n                        <a id=\"linkId\" name=\"linkName\" class=\"linkClass\"\n\t\t\t\t\t\t   href=\"\">Link with empty href</a>\n                        <a id=\"linkId\" name=\"linkName\" class=\"linkClass\"\n                           href='http://heliumhq.com/'>heliumhq.com</a>\n                    </td>\n                </tr>\n                <tr>\n                    <td>\n                        <input type=\"radio\" name=\"radioGroup1\"/>\n                        RadioButton 2\n                    </td>\n                    <td class=\"left\">\n                        <input type=\"radio\" checked name=\"radioGroup2\"/>\n                        Left Labeled RadioButton 2\n                    </td>\n                    <td>Input type=<b>T</b>ext:</td>\n                    <td><input type=\"Text\"/></td>\n                </tr>\n                <tr>\n                    <td>\n                        <select>\n                            <option>Select a value...</option>\n                            <option>Value 1</option>\n                        </select>\n                    </td>\n                    <td>&nbsp;Text with leading &amp;nbsp;</td>\n                    <td>\n\t\t\t\t\t\t<input type=\"submit\" value=\"Submit Button\"\n\t\t\t\t\t\t\t   id=\"submitButtonId\" name=\"submitButtonName\"\n\t\t\t\t\t\t\t   class=\"submitButtonClass\"/>\n\t\t\t\t\t</td>\n                    <td><input type=\"button\" value=\"Input Button\"/></td>\n                </tr>\n                <tr>\n                    <td><input type=\"text\" placeholder=\"Placeholder Text Field\" /></td>\n                    <td><input placeholder=\"Placeholder Text Field without type\" /></td>\n                    <td><div role=\"button\">DIV with role=button</div></td>\n                    <td><button>Button tag without type</button></td>\n                </tr>\n                <tr>\n                    <td><span role='link'>Span with role=link</span></td>\n                    <td><img src=\"img/dolphin.jpg\" alt=\"Dolphin\" id=\"imageId\" name=\"imageName\"\n                             class=\"imageClass\"/></td>\n                    <td>\n                        <a href=\"#\" title=\"Link with title\">\n                            <img src=\"img/link_with_title.png\" />\n                        </a>\n                    </td>\n                    <td>\n                        <input type=\"submit\" title=\"submitButtonTitle\" value=\"Submit Button with title\"/>\n                    </td>\n                </tr>\n                <tr>\n                    <td colspan=\"2\">\n                        <label for=\"requiredTextField\">\n                            <span style=\"color: #C00;\">*</span> Required Text Field:\n                        </label>\n                        <input type=\"text\" id=\"requiredTextField\" />\n                    </td>\n                    <td>\n                        Input type=tel:\n                    </td>\n                    <td>\n                        <input type=\"tel\" id=\"inputTypeTel\" />\n                    </td>\n                </tr>\n                <tr>\n                    <td>contenteditable Paragraph:</td>\n                    <td>\n                        <p contenteditable=\"true\" id=\"contenteditableParagraphId\" />\n                    </td>\n                    <td>TextField in iframe:</td>\n                    <td><iframe src='test_gui_elements_iframe.html'></iframe></td>\n                </tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td>\n\t\t\t\t\t\t<div class=\"simple-text\">\n\t\t\t\t\t\t\tYour email's been sent!\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</td>\n\t\t\t\t\t<td>\n\t\t\t\t\t\t<div class=\"simple-text\">\n\t\t\t\t\t\t\tSingle'quote. Double\"quote.\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</td>\n\t\t\t\t\t<td>\n\t\t\t\t\t\t<div class=\"simple-text\">\n\t\t\t\t\t\t\tHe said \"double quotes\".\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</td>\n\t\t\t\t\t<td>VERÖFFENTLICHEN</td>\n\t\t\t\t</tr>\n\t\t\t\t<tr>\n\t\t\t\t\t<td>\n\t\t\t\t\t\t<select id=\"combo1\">\n\t\t\t\t\t\t\t<option value=\"\">Combo1</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<select id=\"combo2\"></select>\n\t\t\t\t\t</td>\n                </tr>\n                <tr>\n                    <td>\n                        Input type=date:\n                    </td>\n                    <td>\n                        <input type=\"date\" id=\"inputTypeDate\" />\n                    </td>\n                    <td>\n                        Input type=time:\n                    </td>\n                    <td>\n                        <input type=\"time\" id=\"inputTypeTime\" />\n                    </td>\n\t\t\t\t</tr>\n\t\t\t</table>\n\t\t</form>\n\t\t<label for=\"scrollable1\">HTML Unordered List:</label>\n\t\t<div id=\"scrollable1\" style=\"overflow: auto;\">\n\t\t\t<ul style=\"list-style-type: none; float: left; margin: 0; padding-left: 15px;\">\n\t\t\t\t<li id=\"listItem1Id\" name=\"listItem1Name\"\n\t\t\t\t\tclass=\"listItem1Class\">ListItem 1</li>\n\t\t\t\t<li>ListItem 2</li>\n\t\t\t</ul>\n\t\t</div>\n        <dl>\n            <dt>\n                <label for=\"labelledTextField\">Labelled Text Field:</label>\n            </dt>\n            <dd>\n                <input type=\"text\" id=\"labelledTextField\" />\n            </dd>\n        </dl>\n\t\t<p style=\"float: left; margin-right: 10px;\">EUR/USD</p>\n\t\t<p style=\"float: left;\">1.3487</p>\n\t\t<br />\n\t\tFree text not surrounded by tags\n\t\t<br />\n\t\tText field labelled by free text:\n\t\t<br />\n        <input type=\"text\" value=\"TF labelled by free text\" />\n        <div class=\"example\">\n          <h3>Checkboxes</h3>\n          <p>\n            <form>\n              <input type=\"checkbox\"> unchecked<br/>\n              <input type=\"checkbox\" checked> checked\n            </form>\n          </p>\n        </div>\n        <div class=\"example\">\n          <h3>Radio buttons</h3>\n          <p>\n            <form>\n              <input type=\"radio\" name=\"sex\" value=\"male\" checked> male<br/>\n              <input type=\"radio\" name=\"sex\" value=\"female\"> female\n            </form>\n          </p>\n        </div>  \n\t</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_gui_elements_iframe.html",
    "content": "<html>\n\t<head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n        <style>\n            body {\n                margin: 0;\n            }\n        </style>\n\t</head>\n\t<body>\n        <input type=\"text\" id=\"textfieldInIframeId\" />\n\t</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_hover.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_hover</title>\n\t<style type=\"text/css\">\n\t\t#nav, #nav ul {\n\t\t\tpadding: 0;\n\t\t\tmargin: 0;\n\t\t\tlist-style: none;\n\t\t}\n\t\t#nav a {\n\t\t\tdisplay: block;\n\t\t\twidth: 10em;\n\t\t}\n\t\t#nav li {\n\t\t\tfloat: left;\n\t\t\twidth: 10em;\n\t\t}\n\t\t#nav li ul {\n\t\t\tposition: absolute;\n\t\t\twidth: 10em;\n\t\t\tleft: -999em;\n\t\t}\n\t\t#nav li:hover ul {\n\t\t\tleft: auto;\n\t\t}\n\t</style>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"default.css\">\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n</head>\n<body>\n\t<ul id=\"nav\">\n\t\t<li>\n\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 1');\">Dropdown 1</a>\n\t\t\t<ul>\n\t\t\t\t<li>\n\t\t\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 1 - Item A');\">\n\t\t\t\t\t\tItem A\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li>\n\t\t\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 1 - Item B');\">\n\t\t\t\t\t\tItem B\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li>\n\t\t\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 1 - Item C');\">\n\t\t\t\t\t\tItem C\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li>\n\t\t\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 1 - Item D');\">\n\t\t\t\t\t\tItem D\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</li>\n\t\t<li>\n\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 2');\"\n\t\t\t   onmouseout=\"setResult('');\">Dropdown 2</a>\n\t\t\t<ul>\n\t\t\t\t<li>\n\t\t\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 2 - Item A');\">\n\t\t\t\t\t\tItem A\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li>\n\t\t\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 2 - Item B');\">\n\t\t\t\t\t\tItem B\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li>\n\t\t\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 2 - Item C');\">\n\t\t\t\t\t\tItem C\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t\t<li>\n\t\t\t\t\t<a href=\"#\" onmouseover=\"setResult('Dropdown 2 - Item D');\">\n\t\t\t\t\t\tItem D\n\t\t\t\t\t</a>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</li>\n\t</ul>\n\t<p id=\"result\" style=\"position: absolute; top: 100px;\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_iframe/iframe.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>test_iframe - iframe</title>\n</head>\n<body>\nThis text is inside an iframe.\n<iframe src=\"nested_iframe.html\"></iframe>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_iframe/main.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>test_iframe - main</title>\n</head>\n<body>\n<iframe src=\"iframe.html\"></iframe>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_iframe/nested_iframe.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>test_iframe - nested iframe</title>\n</head>\n<body>\nThis text is inside a nested iframe.\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_implicit_wait.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_auto_wait</title>\n\t<script type=\"text/javascript\">\n\t\tfunction createSecondButton() {\n\t\t\tvar secondButton = document.createElement(\"button\");\n\t\t\tsecondButton.innerHTML = \"Now click me!\";\n\t\t\tsecondButton.onclick = createResultPara;\n\t\t\tappendToBody(secondButton);\n\t\t}\n\t\tfunction createResultPara() {\n\t\t\tvar resultPara = document.createElement(\"p\");\n\t\t\tresultPara.innerHTML = \"Success!\";\n\t\t\tresultPara.id = \"result\";\n\t\t\tappendToBody(resultPara);\n\t\t}\n\t\tfunction appendToBody(element) {\n\t\t\tvar body = document.getElementsByTagName('body')[0];\n\t\t\tbody.appendChild(element);\n\t\t}\n\t</script>\n</head>\n<body>\n\t<button onclick=\"setTimeout(createSecondButton, 5000);\">Click me!</button>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_leaked_password.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>Leaked Password Test</title>\n</head>\n<body>\n\t<form method=\"GET\">\n\t\t<table>\n\t\t\t<tr>\n\t\t\t\t<td>Username:</td>\n\t\t\t\t<td><input id=\"username\" name=\"username\" type=\"text\"/></td>\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td>Password:</td>\n\t\t\t\t<td><input id=\"password\" name=\"password\" type=\"password\"/></td>\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td colspan=\"2\">\n\t\t\t\t\t<button type=\"submit\" id=\"submitBtn\">Submit</button>\n\t\t\t\t</td>\n\t\t\t</tr>\n\t\t</table>\n\t</form>\n\t<div id=\"result\" style=\"margin-top: 20px;\"></div>\n\n\t<script>\n\t\twindow.onload = function() {\n\t\t\tvar params = new URLSearchParams(window.location.search);\n\t\t\tvar username = params.get('username');\n\t\t\tvar password = params.get('password');\n\t\t\tvar resultDiv = document.getElementById('result');\n\n\t\t\tif (username && password) {\n\t\t\t\tresultDiv.innerHTML = \n\t\t\t\t\t'You logged in with ' + username + ':' + password;\n\t\t\t}\n\t\t};\n\t</script>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_point.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_point</title>\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/jquery.min.js\"></script>\n\t<script type=\"text/javascript\">\n\t\tfunction getMouseOffset(event){\n\t\t\t// Taken / modified from http://stackoverflow.com/a/5932203/1839209.\n\t\t\tvar totalOffsetX = 0;\n\t\t\tvar totalOffsetY = 0;\n\t\t\tvar currElement = event.target;\n\n\t\t\tdo {\n\t\t\t\ttotalOffsetX += currElement.offsetLeft - currElement.scrollLeft;\n\t\t\t\ttotalOffsetY += currElement.offsetTop - currElement.scrollTop;\n\t\t\t}\n\t\t\twhile(currElement = currElement.offsetParent);\n\n\t\t\treturn {x:event.pageX - totalOffsetX, y:event.pageY - totalOffsetY}\n\t\t}\n\t\tfunction updateResult(evt, action) {\n\t\t\tvar offset = getMouseOffset(evt);\n\t\t\tsetResult(\n\t\t\t\tevt.target.innerHTML + ' ' + action + ' at offset (' +\n\t\t\t\toffset.x + ', ' + offset.y + ').'\n\t\t\t);\n\t\t}\n\t\t$(document).ready(function() {\n\t\t\t$(\"button\").click(function(evt) {\n\t\t\t\tupdateResult(evt, 'clicked');\n\t\t\t\t$(\"button\").off(\"mousemove\");\n\t\t\t});\n\t\t\t$(\"button\").mousemove(function(evt) {\n\t\t\t\tupdateResult(evt, 'hovered');\n\t\t\t});\n\t\t\t$(\"button\").mousedown(function(evt) {\n\t\t\t\tif (evt.which == 3) {\n\t\t\t\t\tupdateResult(evt, 'rightclicked');\n\t\t\t\t\t$(\"button\").off(\"mousemove\");\n\t\t\t\t}\n\t\t\t});\n\t\t\t$(\"button\").contextmenu(function(evt) {\n\t\t\t\tevt.preventDefault();\n\t\t\t\tevt.stopPropagation();\n\t\t\t\treturn false;\n\t\t\t});\n\t\t\t$(\"button\").dblclick(function(evt) {\n\t\t\t\tupdateResult(evt, 'doubleclicked');\n\t\t\t\t$(\"button\").off(\"mousemove\");\n\t\t\t});\n\t\t});\n\t</script>\n\t<style type=\"text/css\">\n\t\tbody, table, tbody, tr, td, button {\n\t\t\tpadding: 0;\n\t\t\tmargin: 0;\n\t\t}\n\t\tbutton {\n\t\t\twidth: 100px;\n\t\t\theight: 30px;\n\t\t}\n\t</style>\n</head>\n<body>\n<table style=\"width: 200px; height: 60px;\">\n\t<tr>\n\t\t<td><button>Button 1</button></td>\n\t\t<td><button>Button 2</button></td>\n\t</tr>\n\t<tr>\n\t\t<td><button>Button 3</button></td>\n\t\t<td><button>Button 4</button></td>\n\t</tr>\n</table>\n<p id=\"result\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_rightclick.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_rightclick</title>\n\t<link type=\"text/css\" rel=\"stylesheet\"\n\t\t  href=\"http://code.jquery.com/ui/1.10.1/themes/base/jquery-ui.css\" />\n\t<script type=\"text/javascript\" src=\"js/util.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/jquery.min.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/jquery-ui.min.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/jquery.ui-contextmenu.js\"></script>\n\t<script type=\"text/javascript\">\n\t\t$(document).ready(function() {\n\t\t\t$('#normalRightclick').mousedown(function(event) {\n\t\t\t\tif (event.which == 3) {\n\t\t\t\t\tsetResult(\"Normal rightclick performed.\");\n\t\t\t\t}\n\t\t\t});\n\t\t\t// Disable context menu \"Inspect element\" etc.:\n\t\t\tdocument.getElementById('normalRightclick').oncontextmenu =\n\t\t\t\t\tfunction(event) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t};\n\t\t\t$(\"#contextMenu\").contextmenu({\n\t\t\t\tmenu: [\n\t\t\t\t\t{title: \"Normal item\", cmd: \"Normal item\"},\n\t\t\t\t\t{title: \"Item with sub items\", cmd: \"Item with sub items\",\n\t\t\t\t\t\tchildren: [\n\t\t\t\t\t\t\t{title: \"Sub item 1\", cmd: \"Sub item 1\"},\n\t\t\t\t\t\t\t{title: \"Sub item 2\", cmd: \"Sub item 2\"}\n\t\t\t\t\t\t]\n\t\t\t\t\t}],\n\t\t\t\t// Don't \"slide in\". On Chrome, Helium sometimes clicks too\n\t\t\t\t// early (when the default \"sliding in\" is not yet complete), in\n\t\t\t\t// which case the test fails:\n\t\t\t\tshow: 0,\n\t\t\t\tselect: function(event, ui) {\n\t\t\t\t\tsetResult(ui.cmd + ' selected.');\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t</script>\n</head>\n<body>\n\t<p id=\"normalRightclick\">Perform a normal rightclick here.</p>\n\t<p id=\"contextMenu\">Rightclick here for context menu.</p>\n\t<p id=\"result\"></p>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_scroll.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title></title>\n</head>\n<body>\n\t<div style=\"width: 5000px; height: 5000px; margin: 10px;\n\t\tpadding: 10px; border: 1px solid #aaaaaa;\">\n\t\tThis is a 5000 x 5000 pixel div\n\t</div>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_start_go_to.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_start_go_to</title>\n</head>\n<body>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_tables.html",
    "content": "<html>\n    <body>\n        <div>\n            <h2>Table no. 1</h2>\n            <table>\n                <thead>\n                    <tr>\n                        <th>T1H1</th>\n                        <th>T1H2</th>\n                        <th>T1H3</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    <tr>\n                        <td>T1R1C1</td>\n                        <td>T1R1C2</td>\n                        <td>T1R1C3</td>\n                    </tr>\n                    <tr>\n                        <td>T1R2C1</td>\n                        <td>T1R2C2</td>\n                        <td>T1R2C3</td>\n                    </tr>\n                    <tr>\n                        <td>T1R3C1</td>\n                        <td>T1R3C2</td>\n                        <td>T1R3C3</td>\n                    </tr>\n                </tbody>\n            </table>\n        </div>\n        <div>\n            <h2>Table no. 2</h2>\n            <table>\n                <thead>\n                    <tr>\n                        <th>T2H1</th>\n                        <th>T2H2</th>\n                        <th>T2H3</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    <tr>\n                        <td>T2R1C1</td>\n                        <td>T2R1C2</td>\n                        <td>T2R1C3</td>\n                    </tr>\n                    <tr>\n                        <td>T2R2C1</td>\n                        <td>T2R2C2</td>\n                        <td>T2R2C3</td>\n                    </tr>\n                    <tr>\n                        <td>T2R3C1</td>\n                        <td>T2R3C2</td>\n                        <td>T2R3C3</td>\n                    </tr>\n                </tbody>\n            </table>\n        </div>\n        <div>\n            <h2>Table no. 3</h2>\n            <table>\n                <thead>\n                    <tr>\n                        <th>T3H1</th>\n                        <th>T3H2</th>\n                        <th>T3H3</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    <tr>\n                        <td>T3R1C1</td>\n                        <td>T3R1C2</td>\n                        <td>T3R1C3</td>\n                    </tr>\n                    <tr>\n                        <td>T3R2C1</td>\n                        <td>T3R2C2</td>\n                        <td>T3R2C3</td>\n                    </tr>\n                    <tr>\n                        <td>T3R3C1</td>\n                        <td>T3R3C2</td>\n                        <td>T3R3C3</td>\n                    </tr>\n                </tbody>\n            </table>\n        </div>\n        <div>\n            <h2>User email addresses</h2>\n            <table>\n                <thead>\n                    <tr>\n                        <th>Name</th>\n                        <th>Email</th>\n                        <th>Country</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    <tr>\n                        <td>John</td>\n                        <td>email1@domain.com</td>\n                        <td>USA</td>\n                    </tr>\n                    <tr>\n                        <td>Abdul</td>\n                        <td>email2@domain.com</td>\n                        <td>Yemen</td>\n                    </tr>\n                    <tr>\n                        <td>Chang</td>\n                        <td>email3@domain.com</td>\n                        <td>China</td>\n                    </tr>\n                </tbody>\n            </table>\n        </div>\n    </body>\n</html>\n"
  },
  {
    "path": "tests/api/data/test_text_impl.html",
    "content": "<!DOCTYPE html>\n<html>\n<body>\n<p>A paragraph</p>\n<div>\n\t<p>A paragraph inside a div</p>\n\t<p>Another paragraph inside the div</p>\n\t<p></p>\n</div>\n<div>\n\t<label>\n\t\t<select>\n\t\t\t<option>An option</option>\n\t\t</select>\n\t</label>\n</div>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_wait_until.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_wait_until</title>\n\t<script type=\"text/javascript\">\n\t\tfunction createResultPara() {\n\t\t\tvar resultPara = document.createElement(\"p\");\n\t\t\tresultPara.innerHTML = \"Success!\";\n\t\t\tresultPara.id = \"result\";\n\n\t\t\tvar body = document.getElementsByTagName('body')[0];\n\t\t\tbody.appendChild(resultPara);\n\t\t}\n\t</script>\n</head>\n<body>\n\t<button onclick=\"setTimeout(createResultPara, 1500);\">Click me!</button>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_window/popup.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window - popup</title>\n</head>\n<body>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_window/test_window.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window</title>\n</head>\n<body>\n\t<a href=\"popup.html\" target=\"_blank\">Click here to open a popup.</a>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_window_handling/main.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window_handling - Main</title>\n\t<script type=\"text/javascript\" src=\"../js/jquery.min.js\"></script>\n\t<script type=\"text/javascript\">\n\t\t$(document).ready(function() {\n\t\t\t// IE doesn't support property 'autofocus', so we need to use JS to\n\t\t\t// give focus to the text field.\n\t\t\t$('#mainTextField').focus();\n\t\t});\n\t</script>\n</head>\n<body>\n\tText field: <input type=\"text\" autofocus id=\"mainTextField\">\n\t<br/>\n\t<a href=\"popup.html\" target=\"_blank\">Open popup</a>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_window_handling/main_immediate_popup.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window_handling - Main (immediate popup)</title>\n\t<script type=\"text/javascript\" src=\"../js/jquery.min.js\"></script>\n\t<script type=\"text/javascript\">\n\t\t$(document).ready(function() {\n\t\t\twindow.open(\"popup.html\");\n\t\t});\n\t</script>\n</head>\n<body>\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_window_handling/popup.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_window_handling - Popup</title>\n\t<script type=\"text/javascript\" src=\"../js/jquery.min.js\"></script>\n\t<script type=\"text/javascript\">\n\t\t$(document).ready(function() {\n\t\t\t// IE doesn't support property 'autofocus', so we need to use JS to\n\t\t\t// give focus to the text field.\n\t\t\t$('#popupTextField').focus();\n\t\t});\n\t</script>\n</head>\n<body>\n\tIn popup.\n\t<br/>\n\tText field: <input type=\"text\" autofocus id=\"popupTextField\">\n</body>\n</html>"
  },
  {
    "path": "tests/api/data/test_write.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>test_write</title>\n\t<script type=\"text/javascript\" src=\"js/jquery.min.js\"></script>\n\t<script type=\"text/javascript\">\n\t\t$(document).ready(function() {\n\t\t\t// IE doesn't support property 'autofocus', so we need to use JS to\n\t\t\t// give focus to the text field.\n\t\t\t$('#autofocus').focus();\n\t\t});\n\t</script>\n</head>\n<body>\n\t<table>\n\t\t<tr>\n\t\t\t<td>Autofocus text field:</td>\n\t\t\t<td><input id=\"autofocus\" type=\"text\" autofocus/></td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td>Normal text field:</td>\n\t\t\t<td><input id=\"normal\" type=\"text\"/></td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\tInput type=date:\n\t\t\t</td>\n\t\t\t<td>\n\t\t\t\t<input type=\"date\" id=\"inputTypeDate\" />\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\tInput type=time:\n\t\t\t</td>\n\t\t\t<td>\n\t\t\t\t<input type=\"time\" id=\"inputTypeTime\" />\n\t\t\t</td>\n\t\t</tr>\n\t</table>\n</body>\n</html>"
  },
  {
    "path": "tests/api/test_alert.py",
    "content": "from helium import click, Alert, press, ENTER, write, TextField, Config, \\\n\twait_until\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom helium._impl.util.system import is_mac\nfrom tests.api import BrowserAT, test_browser_name\nfrom selenium.common.exceptions import UnexpectedAlertPresentException\nfrom time import time, sleep\nfrom unittest import skipIf\n\nimport selenium\n\nclass AlertAT():\n\n\tUNEXPECTED_ALERT_PRESENT_EXCEPTION_MSG = \\\n\t\t\"This command is not supported when an alert is present. To accept \" \\\n\t\t\"the alert (this usually corresponds to clicking 'OK') use `Alert().\" \\\n\t\t\"accept()`. To dismiss the alert (ie. 'cancel' it), use `Alert().\" \\\n\t\t\"dismiss()`. If the alert contains a text field, you can use \" \\\n\t\t\"write(...) to set its value. Eg.: `write('hi there!')`.\"\n\n\tdef get_page(self):\n\t\treturn 'test_alert.html'\n\tdef get_link_to_open_alert(self):\n\t\traise NotImplementedError()\n\tdef get_expected_alert_text(self):\n\t\traise NotImplementedError()\n\tdef get_expected_alert_accepted_result(self):\n\t\traise NotImplementedError()\n\tdef get_expected_alert_dismissed_result(self):\n\t\treturn self.get_expected_alert_accepted_result()\n\tdef setUp(self):\n\t\tsuper(AlertAT, self).setUp()\n\t\tclick(self.get_link_to_open_alert())\n\t\twait_until(Alert().exists)\n\tdef tearDown(self):\n\t\tif Alert().exists():\n\t\t\t# We need to call .accept() instead of .dismiss() here to work\n\t\t\t# around ChromeDriver bug 764:\n\t\t\t# https://code.google.com/p/chromedriver/issues/detail?id=764\n\t\t\tAlert().accept()\n\t\tsuper(AlertAT, self).tearDown()\n\tdef test_alert_exists(self):\n\t\tself.assertTrue(Alert().exists())\n\tdef test_alert_text_exists(self):\n\t\tself.assertTrue(Alert(self.get_expected_alert_text()).exists())\n\tdef test_alert_text_not_exists(self):\n\t\tself.assertFalse(Alert('Wrong text').exists())\n\tdef test_alert_text(self):\n\t\tself.assertEqual(self.get_expected_alert_text(), Alert().text)\n\tdef test_alert_accept(self):\n\t\tAlert().accept()\n\t\tself._expect_result(self.get_expected_alert_accepted_result())\n\t@skipIf(\n\t\tis_mac() and test_browser_name() == 'chrome',\n\t\t\"Chrome driver on OSX does not support dismissing JS alerts. \" +\n\t\t\"See: https://code.google.com/p/chromedriver/issues/detail?id=764\"\n\t)\n\tdef test_alert_dismiss(self):\n\t\tAlert().dismiss()\n\t\tself._expect_result(self.get_expected_alert_dismissed_result())\n\tdef test_click_with_open_alert_raises_exception(self):\n\t\twith self.assertRaises(UnexpectedAlertPresentException) as cm:\n\t\t\tclick(\"OK\")\n\t\tmsg = self._get_unhandled_alert_exception_msg(cm.exception)\n\t\tself.assertEqual(\n\t\t\tself.UNEXPECTED_ALERT_PRESENT_EXCEPTION_MSG, msg\n\t\t)\n\tdef test_press_with_open_alert_raises_exception(self):\n\t\twith self.assertRaises(UnexpectedAlertPresentException) as cm:\n\t\t\tpress(ENTER)\n\t\tmsg = self._get_unhandled_alert_exception_msg(cm.exception)\n\t\tself.assertEqual(\n\t\t\tself.UNEXPECTED_ALERT_PRESENT_EXCEPTION_MSG, msg\n\t\t)\n\t\"\"\"\n\tThis method waits up to one second for the given result to appear. It\n\tshould not be needed but Chrome sometimes returns from .accept()/.dismiss()\n\tbefore the JavaScript in test_alert.html has set the corresponding\n\tresult.\n\t\"\"\"\n\tdef _expect_result(self, expected_result, timeout_secs=1):\n\t\tstart_time = time()\n\t\twhile time() < start_time + timeout_secs:\n\t\t\tactual_result = self.read_result_from_browser(timeout_secs=.3)\n\t\t\tif actual_result == expected_result:\n\t\t\t\treturn\n\t\t\tsleep(0.2)\n\t\tself.assertEqual(expected_result, actual_result)\n\tdef _get_unhandled_alert_exception_msg(self, e):\n\t\tif selenium.__version__ == '2.43.0':\n\t\t\t# Selenium 2.43.0 has a regression where accessing the .msg field\n\t\t\t# of an UnexpectedAlertPresentException raises an AttributeError -\n\t\t\t# See: https://code.google.com/p/selenium/issues/detail?id=7886\n\t\t\treturn e.args[0]\n\t\telse:\n\t\t\treturn e.msg\n\nclass AlertTest(AlertAT, BrowserAT):\n\tdef get_link_to_open_alert(self):\n\t\treturn 'Display alert'\n\tdef get_expected_alert_text(self):\n\t\treturn 'Hello World!'\n\tdef get_expected_alert_accepted_result(self):\n\t\treturn 'Alert displayed'\n\nclass ConfirmationDialogTest(AlertAT, BrowserAT):\n\tdef get_link_to_open_alert(self):\n\t\treturn 'Ask for confirmation'\n\tdef get_expected_alert_text(self):\n\t\treturn 'Proceed?'\n\tdef get_expected_alert_accepted_result(self):\n\t\treturn 'Accepted'\n\tdef get_expected_alert_dismissed_result(self):\n\t\treturn 'Dismissed'\n\nclass PromptTest(AlertAT, BrowserAT):\n\tdef get_link_to_open_alert(self):\n\t\treturn 'Prompt for value'\n\tdef get_expected_alert_text(self):\n\t\treturn 'Please enter a value'\n\tdef get_expected_alert_accepted_result(self):\n\t\treturn 'Value entered: '\n\tdef test_write_value(self):\n\t\twrite(\"1\")\n\t\tAlert().accept()\n\t\tself._expect_result('Value entered: 1')\n\tdef test_write_into_label_raises_exception(self):\n\t\twith self.assertRaises(UnexpectedAlertPresentException) as cm:\n\t\t\twrite(\"3\", into=\"Please enter a value\")\n\t\tmsg = self._get_unhandled_alert_exception_msg(cm.exception)\n\t\tself.assertEqual(\n\t\t\tself.UNEXPECTED_ALERT_PRESENT_EXCEPTION_MSG, msg\n\t\t)\n\tdef test_write_into_text_field_raises_exception(self):\n\t\twith self.assertRaises(UnexpectedAlertPresentException) as cm:\n\t\t\twrite(\"4\", into=TextField(\"Please enter a value\"))\n\t\tmsg = self._get_unhandled_alert_exception_msg(cm.exception)\n\t\tself.assertEqual(\n\t\t\tself.UNEXPECTED_ALERT_PRESENT_EXCEPTION_MSG, msg\n\t\t)\n\tdef test_write_into_non_existent_label_raises_exception(self):\n\t\twith self.assertRaises(UnexpectedAlertPresentException) as cm:\n\t\t\twrite(\"5\", into=\"Please enter a value\")\n\t\tmsg = self._get_unhandled_alert_exception_msg(cm.exception)\n\t\tself.assertEqual(\n\t\t\tself.UNEXPECTED_ALERT_PRESENT_EXCEPTION_MSG, msg\n\t\t)\n\tdef test_write_into_alert(self):\n\t\twrite(\"7\", into=Alert())\n\t\tAlert().accept()\n\t\tself._expect_result(\"Value entered: 7\")\n\tdef test_write_into_labelled_alert(self):\n\t\twrite(\"8\", into=Alert(self.get_expected_alert_text()))\n\t\tAlert().accept()\n\t\tself._expect_result(\"Value entered: 8\")\n\tdef test_write_into_non_existent_alert(self):\n\t\twith TemporaryAttrValue(Config, 'implicit_wait_secs', 1):\n\t\t\twith self.assertRaises(LookupError):\n\t\t\t\twrite(\"8\", into=Alert(\"Non-existent\"))"
  },
  {
    "path": "tests/api/test_aria.py",
    "content": "from helium import Button, TextField\nfrom tests.api import BrowserAT\n\nclass AriaTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_aria.html'\n\tdef test_aria_label_button_exists(self):\n\t\tself.assertTrue(Button(\"Close\").exists())\n\tdef test_aria_label_button_is_enabled(self):\n\t\tself.assertTrue(Button(\"Close\").is_enabled())\n\tdef test_aria_label_disabled_button_is_enabled(self):\n\t\tself.assertFalse(Button(\"Disabled Close\").is_enabled())\n\tdef test_aria_label_non_existent_button(self):\n\t\tself.assertFalse(Button(\"This doesnt exist\").exists())\n\tdef test_aria_label_div_button_exists(self):\n\t\tself.assertTrue(Button(\"Attach files\").exists())\n\tdef test_aria_label_div_button_is_enabled(self):\n\t\tself.assertTrue(Button(\"Attach files\").is_enabled())\n\tdef test_aria_label_div_disabled_button_is_enabled(self):\n\t\tself.assertFalse(Button(\"Disabled Attach files\").is_enabled())\n\tdef test_aria_label_submit_button_exists(self):\n\t\tself.assertTrue(Button(\"Submit\").exists())\n\tdef test_aria_textbox_exists(self):\n\t\tself.assertTrue(TextField(\"Textbox\").exists())\n\tdef test_aria_textbox_value(self):\n\t\tself.assertEqual(\"Textbox value\", TextField(\"Textbox\").value)"
  },
  {
    "path": "tests/api/test_chrome_options.py",
    "content": "from helium import start_chrome, kill_browser\nfrom os.path import join\nfrom tests.api import test_browser_name\nfrom unittest import TestCase, skipIf\nfrom selenium.webdriver.chrome.options import Options as ChromeOptions\nfrom contextlib import contextmanager\n\nimport json\n\n@skipIf(test_browser_name() != 'chrome', 'Only run this test for Chrome')\nclass ChromeOptionsTest(TestCase):\n\tdef test_start_chrome_does_not_override_custom_prefs(self):\n\t\ttest_value = 'download.default_directory'\n\t\tprefs = {'download.default_directory': test_value}\n\t\twith chrome_with_prefs(prefs) as actual_prefs:\n\t\t\tself.assertEqual(\n\t\t\t\ttest_value,\n\t\t\t\tactual_prefs['download']['default_directory']\n\t\t\t)\n\tdef test_start_chrome_respects_custom_password_leak_detection(self):\n\t\tprefs = {'profile.password_manager_leak_detection': True}\n\t\twith chrome_with_prefs(prefs) as actual_prefs:\n\t\t\tself.assertTrue(\n\t\t\t\tactual_prefs['profile']['password_manager_leak_detection']\n\t\t\t)\n\n@contextmanager\ndef chrome_with_prefs(prefs):\n\ttry:\n\t\toptions = ChromeOptions()\n\t\toptions.add_experimental_option('prefs', prefs)\n\t\tdriver = start_chrome(headless=True, options=options)\n\t\tuser_data_dir = driver.capabilities['chrome']['userDataDir']\n\t\tprefs_file = join(user_data_dir, 'Default', 'Preferences')\n\t\twith open(prefs_file, 'r') as f:\n\t\t\tyield json.load(f)\n\tfinally:\n\t\tkill_browser()"
  },
  {
    "path": "tests/api/test_click.py",
    "content": "from helium import click, Config\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom tests.api import BrowserAT\n\nclass ClickTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_click.html'\n\tdef test_click(self):\n\t\tclick(\"Click me!\")\n\t\tself.assertEqual('Success!', self.read_result_from_browser())\n\tdef test_click_non_existent_element(self):\n\t\twith TemporaryAttrValue(Config, 'implicit_wait_secs', 1):\n\t\t\twith self.assertRaises(LookupError):\n\t\t\t\tclick(\"Non-existent\")"
  },
  {
    "path": "tests/api/test_doubleclick.py",
    "content": "from helium import doubleclick\nfrom tests.api import BrowserAT\n\nclass DoubleclickTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_doubleclick.html'\n\tdef test_double_click(self):\n\t\tdoubleclick('Doubleclick here.')\n\t\tself.assertEqual('Success!', self.read_result_from_browser())"
  },
  {
    "path": "tests/api/test_drag.py",
    "content": "from helium import *\nfrom selenium.webdriver.common.by import By\nfrom tests.api import BrowserAT\n\nclass DragTest(BrowserAT):\n\tdef setUp(self):\n\t\tsuper().setUp()\n\t\tself.drag_target = self.driver.find_element(By.ID, 'target')\n\tdef get_page(self):\n\t\treturn 'test_drag/default.html'\n\tdef test_drag(self):\n\t\tdrag(\"Drag me.\", to=self.drag_target)\n\t\tself.assertEqual('Success!', self.read_result_from_browser())\n\tdef test_drag_to_point(self):\n\t\ttarget_loc = self.drag_target.location\n\t\ttarget_size = self.drag_target.size\n\t\ttarget_point = Point(\n\t\t\ttarget_loc['x'] + target_size['width'] / 2,\n\t\t\ttarget_loc['y'] + target_size['height'] / 2\n\t\t)\n\t\tself.assertTrue(Text('Drag me').exists())\n\t\tdrag(\"Drag me.\", to=target_point)\n\t\tself.assertEqual('Success!', self.read_result_from_browser())\n\nclass Html5DragIT(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_drag/html5.html'\n\tdef test_html5_drag(self):\n\t\tdrag(\"Drag me.\", to=self.driver.find_element(By.ID, 'target'))\n\t\tself.assertEqual('Success!', self.read_result_from_browser())"
  },
  {
    "path": "tests/api/test_file_upload.py",
    "content": "from helium import attach_file, drag_file, TextField, Text\nfrom tests.api import BrowserAT\nfrom tests.api.util import get_data_file\n\nclass FileUploadTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_file_upload/test_file_upload.html'\n\tdef setUp(self):\n\t\tsuper().setUp()\n\t\tself.file_to_upload = get_data_file(\n\t\t\t'test_file_upload', 'upload_this.png'\n\t\t)\n\tdef test_normal_file_upload_is_not_text_field(self):\n\t\tself.assertFalse(TextField(\"Normal file upload\").exists())\n\tdef test_attach_file_to_normal_file_upload(self):\n\t\tattach_file(self.file_to_upload, to='Normal file upload')\n\t\tself.assertEqual('Success!', self.read_result_from_browser())\n\tdef test_attach_file_no_to(self):\n\t\tattach_file(self.file_to_upload)\n\t\tself.assertEqual('Success!', self.read_result_from_browser())\n\tdef test_attach_file_to_point(self):\n\t\tattach_file(\n\t\t\tself.file_to_upload,\n\t\t\tto=Text('Normal file upload').top_left + (200, 10)\n\t\t)\n\t\tself.assertEqual('Success!', self.read_result_from_browser())\n\tdef test_drag_file_to_appearing_drop_area(self):\n\t\tdrag_file(self.file_to_upload, to='Drop the file here!')\n\t\tself.assertEqual('Success!', self.read_result_from_browser())"
  },
  {
    "path": "tests/api/test_find_all.py",
    "content": "from selenium.common.exceptions import StaleElementReferenceException\nfrom helium import find_all, Button, TextField, write\nfrom tests.api import BrowserAT\n\nclass FindAllTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_gui_elements.html'\n\tdef test_find_all_duplicate_button(self):\n\t\tself.assertEqual(4, len(find_all(Button(\"Duplicate Button\"))))\n\tdef test_find_all_duplicate_button_to_right_of(self):\n\t\tself.assertEqual(\n\t\t\t2, len(find_all(Button(\"Duplicate Button\", to_right_of=\"Row 1\")))\n\t\t)\n\tdef test_find_all_duplicate_button_below_to_right_of(self):\n\t\tself.assertEqual(\n\t\t\t1, len(find_all(Button(\n\t\t\t\t\"Duplicate Button\", below=\"Column 1\", to_right_of=\"Row 1\"\n\t\t\t)))\n\t\t)\n\tdef test_find_all_nested_search_areas(self):\n\t\t# `test_find_all_duplicate_button_below_to_right_of` above showed that\n\t\t# there is only one button that fulfills the following criteria:\n\t\tbutton = \\\n\t\t\tButton(\"Duplicate Button\", below=\"Column 1\", to_right_of=\"Row 1\")\n\t\t# Similarly, there should only be one button below this button:\n\t\tself.assertEqual(\n\t\t\t1, len(find_all(Button(\"Duplicate Button\", below=button)))\n\t\t)\n\tdef test_find_all_non_existent_button(self):\n\t\tself.assertEqual([], find_all(Button(\"Non-existent Button\")))\n\tdef test_find_all_yields_api_elements(self):\n\t\tself.assertIsInstance(\n\t\t\tfind_all(TextField('Example Text Field'))[0], TextField\n\t\t)\n\tdef test_interact_with_found_elements(self):\n\t\tall_tfs = find_all(TextField())\n\t\texample_tf = None\n\t\tfor text_field in all_tfs:\n\t\t\ttry:\n\t\t\t\tid_ = text_field.web_element.get_attribute('id')\n\t\t\texcept StaleElementReferenceException:\n\t\t\t\t# This may happen for found web elements in different iframes.\n\t\t\t\t# TODO: Improve this, eg. by adding a .getId() property to\n\t\t\t\t# TextField (/HTMLElement) which handles this problem.\n\t\t\t\tpass\n\t\t\telse:\n\t\t\t\tif id_ == 'exampleTextFieldId':\n\t\t\t\t\texample_tf = text_field\n\t\tself.assertIsNotNone(example_tf)\n\t\twrite(\"test_interact_with_found_elements\", into=example_tf)\n\t\tself.assertEqual(\n\t\t\t\"test_interact_with_found_elements\",\n\t\t\tTextField(\"Example Text Field\").value\n\t\t)\n\tdef test_bound_element_as_spatial_constraint(self):\n\t\tcounts = []\n\t\tfor button in find_all(Button(\"Duplicate Button\")):\n\t\t\tbelow_this = find_all(Button(\"Duplicate Button\", below=button))\n\t\t\tcounts.append(len(below_this))\n\t\tself.assertEqual(\n\t\t\tsorted(counts), [0, 0, 1, 1],\n\t\t\t\"Two buttons (those in row 1) have 1 button below, two (those in \"\n\t\t\t\"row 2) have 0.\"\n\t\t)\n\tdef test_very_nested_search_areas(self):\n\t\tself.assertEqual(\n\t\t\t1,\n\t\t\tlen(find_all(\n\t\t\t\tButton('Duplicate Button', to_left_of=Button(\n\t\t\t\t\t'Duplicate Button', below=Button('Duplicate Button')\n\t\t\t\t))\n\t\t\t))\n\t\t)"
  },
  {
    "path": "tests/api/test_gui_elements.py",
    "content": "# -*- coding: utf-8 -*-\nfrom helium import Button, TextField, ComboBox, CheckBox, click, \\\n\tRadioButton, write, Text, find_all, Link, ListItem, Image, select, Config\nfrom tests.api import BrowserAT\n\nclass GUIElementsTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_gui_elements.html'\n\t@classmethod\n\tdef setUpClass(cls):\n\t\tsuper().setUpClass()\n\t\t# If a test does fail, ensure it happens quickly:\n\t\tcls.implicit_wait_secs_before = Config.implicit_wait_secs\n\t\tConfig.implicit_wait_secs = .5\n\t@classmethod\n\tdef tearDownClass(cls):\n\t\tConfig.implicit_wait_secs = cls.implicit_wait_secs_before\n\t\tsuper().tearDownClass()\n\n\t# Button tests:\n\tdef test_button_exists(self):\n\t\tself.assertTrue(Button(\"Enabled Button\").exists())\n\tdef test_submit_button_exists(self):\n\t\tself.assertTrue(Button(\"Submit Button\").exists())\n\tdef test_submit_button_exists_lower_case(self):\n\t\tself.assertTrue(Button(\"submit button\").exists())\n\tdef test_input_button_exists(self):\n\t\tself.assertTrue(Button(\"Input Button\").exists())\n\tdef test_button_not_exists(self):\n\t\tself.assertFalse(Button(\"Nonexistent Button\").exists())\n\tdef test_text_field_does_not_exist_as_button(self):\n\t\tself.assertFalse(Button(\"Example Text Field\").exists())\n\tdef test_enabled_button(self):\n\t\tself.assertIs(True, Button(\"Enabled Button\").is_enabled())\n\tdef test_disabled_button(self):\n\t\tself.assertFalse(Button(\"Disabled Button\").is_enabled())\n\tdef test_button_no_text(self):\n\t\tself.assertEqual(2, len(find_all(Button(to_right_of='Row 1'))))\n\tdef test_div_button_exists(self):\n\t\tself.assertTrue(Button(\"DIV with role=button\").exists())\n\tdef test_button_tag_button_exists(self):\n\t\tself.assertTrue(Button(\"Button tag without type\").exists())\n\tdef test_submit_button_can_be_found_by_title(self):\n\t\tself.assertTrue(Button(\"submitButtonTitle\").exists())\n\n\t# TextField tests:\n\tdef test_text_field_exists(self):\n\t\tself.assertIs(True, TextField(\"Example Text Field\").exists())\n\tdef test_text_field_lower_case_exists(self):\n\t\tself.assertIs(True, TextField(\"example text field\").exists())\n\tdef test_text_field_in_second_col_exists(self):\n\t\tself.assertIs(True, TextField(\"Another Text Field\").exists())\n\tdef test_text_field_not_exists(self):\n\t\tself.assertFalse(TextField(\"Nonexistent TextField\").exists())\n\tdef test_text_field_is_editable_false(self):\n\t\tself.assertIs(False, TextField(\"ReadOnly Text Field\").is_editable())\n\tdef test_text_field_is_editable(self):\n\t\tself.assertTrue(TextField(\"Example Text Field\").is_editable())\n\tdef test_text_field_is_enabled(self):\n\t\tself.assertIs(True, TextField(\"Example Text Field\").is_enabled())\n\tdef test_text_field_is_enabled_false(self):\n\t\tself.assertFalse(TextField(\"Disabled Text Field\").is_enabled())\n\tdef test_text_field_value(self):\n\t\tself.assertEqual(\"Lorem ipsum\", TextField(\"Example Text Field\").value)\n\tdef test_text_field_with_placeholder_exists(self):\n\t\tself.assertIs(True, TextField(\"Placeholder Text Field\").exists())\n\tdef test_text_field_no_type_specified_with_placeholder_exists(self):\n\t\tself.assertIs(\n\t\t\tTrue, TextField(\"Placeholder Text Field without type\").exists()\n\t\t)\n\tdef test_empty_text_field_value(self):\n\t\tself.assertEqual('', TextField(\"Empty Text Field\").value)\n\tdef test_read_readonly_text_field(self):\n\t\tself.assertEqual(\n\t\t\t'This is read only', TextField(\"ReadOnly Text Field\").value\n\t\t)\n\tdef test_read_disabled_text_field(self):\n\t\tself.assertEqual(\n\t\t\t'This is disabled', TextField(\"Disabled Text Field\").value\n\t\t)\n\tdef test_read_german_text_field(self):\n\t\tself.assertEqual(\n\t\t\t'Heizölrückstoßabdämpfung', TextField(\"Deutsch\").value\n\t\t)\n\tdef test_text_field_input_type_upper_case_text(self):\n\t\tself.assertTrue(TextField('Input type=Text').exists())\n\tdef test_write_into_labelled_text_field(self):\n\t\twrite('Some text', into='Labelled Text Field')\n\t\tself.assertEqual('Some text', TextField('Labelled Text Field').value)\n\tdef test_required_text_field_marked_with_asterisk_exists(self):\n\t\tself.assertIs(True, TextField(\"Required Text Field\").exists())\n\tdef test_text_field_labelled_by_free_text(self):\n\t\tself.assertEqual(\n\t\t\t\"TF labelled by free text\",\n\t\t\tTextField(\"Text field labelled by free text\").value\n\t\t)\n\tdef test_input_type_tel(self):\n\t\tself.assertFindsEltWithId(TextField(\"Input type=tel\"), \"inputTypeTel\")\n\tdef test_input_type_date(self):\n\t\tself.assertFindsEltWithId(TextField(\"Input type=date\"), \"inputTypeDate\")\n\tdef test_input_type_time(self):\n\t\tself.assertFindsEltWithId(TextField(\"Input type=time\"), \"inputTypeTime\")\n\tdef test_text_field_to_right_of_text_field(self):\n\t\tself.assertFindsEltWithId(\n\t\t\tTextField(to_right_of=TextField(\"Required Text Field\")),\n\t\t\t\"inputTypeTel\"\n\t\t)\n\tdef test_contenteditable_paragrapth(self):\n\t\tself.assertFindsEltWithId(\n\t\t\tTextField(\"contenteditable Paragraph\"), \"contenteditableParagraphId\"\n\t\t)\n\n\t# ComboBox tests:\n\tdef test_combo_box_exists(self):\n\t\tself.assertIs(True, ComboBox(\"Drop Down List\").exists())\n\tdef test_combo_box_exists_lower_case(self):\n\t\tself.assertIs(True, ComboBox(\"drop down list\").exists())\n\tdef test_drop_down_list_is_editable_false(self):\n\t\tself.assertIs(False, ComboBox(\"Drop Down List\").is_editable())\n\tdef test_editable_combo_box_is_editable(self):\n\t\tself.assertTrue(ComboBox(\"Editable ComboBox\").is_editable())\n\tdef test_combo_box_options(self):\n\t\toptions = ComboBox(\"Drop Down List\").options\n\t\tself.assertListEqual(\n\t\t\toptions, ['Option One', 'Option Two', 'Option Three']\n\t\t)\n\tdef test_reads_value_of_combo_box(self):\n\t\tself.assertEqual('Option One', ComboBox(\"Drop Down List\").value)\n\tdef test_select_value_from_combo_box(self):\n\t\tself.assertEqual('Option One', ComboBox(\"Drop Down List\").value)\n\t\tselect(\"Drop Down List\", \"Option Two\")\n\t\tself.assertEqual('Option Two', ComboBox(\"Drop Down List\").value)\n\t\tselect(ComboBox(\"Drop Down List\"), \"Option Three\")\n\t\tself.assertEqual('Option Three', ComboBox(\"Drop Down List\").value)\n\tdef test_combo_box_identified_by_value(self):\n\t\tcombo_box = ComboBox(\"Select a value...\")\n\t\tself.assertTrue(combo_box.exists())\n\t\tself.assertEqual(\"Select a value...\", combo_box.value)\n\t\tself.assertFalse(combo_box.is_editable())\n\t\tself.assertEqual(\n\t\t\t[\"Select a value...\", \"Value 1\"], combo_box.options\n\t\t)\n\tdef test_combo_box_preceded_by_combo_with_name_as_label(self):\n\t\tself.assertEqual(\n\t\t\t\"combo1\", ComboBox(\"Combo1\").web_element.get_attribute(\"id\")\n\t\t)\n\n\t# CheckBox tests:\n\tdef test_check_box_exists(self):\n\t\tself.assertIs(True, CheckBox(\"CheckBox\").exists())\n\tdef test_check_box_exists_lower_case(self):\n\t\tself.assertIs(True, CheckBox(\"checkbox\").exists())\n\tdef test_left_hand_side_check_box_exists(self):\n\t\tself.assertIs(True, CheckBox(\"LHS CheckBox\").exists())\n\tdef test_check_box_not_exists(self):\n\t\tself.assertFalse(CheckBox(\"Nonexistent CheckBox\").exists())\n\tdef test_text_field_does_not_exist_as_check_box(self):\n\t\tself.assertFalse(CheckBox(\"Empty Text Field\").exists())\n\tdef test_ticked_check_box_exists(self):\n\t\tself.assertIs(True, CheckBox(\"Ticked CheckBox\").exists())\n\tdef test_ticked_check_box_is_enabled(self):\n\t\tself.assertIs(True, CheckBox(\"Ticked CheckBox\").is_enabled())\n\tdef test_right_labelled_check_box_exists(self):\n\t\tself.assertIs(True, CheckBox(\"Right Labeled CheckBox\").exists())\n\tdef test_left_labelled_check_box_exists(self):\n\t\tself.assertIs(True, CheckBox(\"Left Labeled CheckBox\").exists())\n\tdef test_disabled_check_box_exists(self):\n\t\tself.assertIs(True, CheckBox(\"Disabled CheckBox\").exists())\n\tdef test_ticked_check_box_is_checked(self):\n\t\tself.assertIs(True, CheckBox(\"Ticked CheckBox\").is_checked())\n\tdef test_right_labelled_check_box_is_not_checked(self):\n\t\tself.assertFalse(CheckBox(\"Right Labeled CheckBox\").is_checked())\n\tdef test_left_labelled_check_box_is_not_checked(self):\n\t\tself.assertIs(False, CheckBox(\"Left Labeled CheckBox\").is_checked())\n\tdef test_disabled_check_box_is_not_checked(self):\n\t\tself.assertIs(False, CheckBox(\"Disabled CheckBox\").is_checked())\n\tdef test_untick_check_box(self):\n\t\tticked_check_box = CheckBox(\"Ticked CheckBox\")\n\t\tclick(ticked_check_box)\n\t\tself.assertIs(False, ticked_check_box.is_checked())\n\tdef test_disabled_check_box_is_not_enabled(self):\n\t\tself.assertIs(False, CheckBox(\"Disabled CheckBox\").is_enabled())\n\tdef test_check_box_enclosed_by_label(self):\n\t\tself.assertFindsEltWithId(\n\t\t\tCheckBox(\"CheckBox enclosed by label\"), \"checkBoxEnclosedByLabel\"\n\t\t)\n\tdef test_checkboxes_labelled_by_free_text(self):\n\t\tself.assertTrue(CheckBox(\"unchecked\").exists())\n\t\tself.assertTrue(CheckBox(\"checked\").exists())\n\t\tself.assertTrue(CheckBox(\"checked\").is_checked())\n\t\tself.assertFalse(CheckBox(\"unchecked\").is_checked())\n\n\t# RadioButton tests:\n\tdef test_first_radio_button_exists(self):\n\t\tself.assertIs(True, RadioButton(\"RadioButton 1\").exists())\n\tdef test_first_radio_button_exists_lower_case(self):\n\t\tself.assertIs(True, RadioButton(\"radiobutton 1\").exists())\n\tdef test_second_radio_button_exists(self):\n\t\tself.assertIs(True, RadioButton(\"RadioButton 2\").exists())\n\tdef test_left_labelled_radio_button_one_exists(self):\n\t\tself.assertIs(True, RadioButton(\"Left Labeled RadioButton 1\").exists())\n\tdef test_left_labelled_radio_button_two_exists(self):\n\t\tself.assertIs(True, RadioButton(\"Left Labeled RadioButton 2\").exists())\n\tdef test_first_radio_button_is_selected(self):\n\t\tself.assertIs(True, RadioButton(\"RadioButton 1\").is_selected())\n\tdef test_second_radio_button_is_not_selected(self):\n\t\tself.assertIs(False, RadioButton(\"RadioButton 2\").is_selected())\n\tdef test_select_second_radio_button(self):\n\t\tclick(RadioButton(\"RadioButton 2\"))\n\t\tself.assertIs(False, RadioButton(\"RadioButton 1\").is_selected())\n\t\tself.assertIs(True, RadioButton(\"RadioButton 2\").is_selected())\n\tdef test_radio_button_not_exists(self):\n\t\tself.assertIs(False, RadioButton(\"Nonexistent option\").exists())\n\tdef test_text_field_is_not_a_radio_button(self):\n\t\tself.assertIs(False, RadioButton(\"Empty Text Field\").exists())\n\tdef test_radiobuttons_labelled_by_free_text(self):\n\t\tself.assertTrue(RadioButton(\"male\").exists())\n\t\tself.assertTrue(RadioButton(\"female\").exists())\n\t\tself.assertTrue(RadioButton(\"male\").is_selected())\n\t\tself.assertFalse(RadioButton(\"female\").is_selected())\n\n\t# Text tests:\n\tdef test_text_exists_submit_button(self):\n\t\tself.assertTrue(Text(\"Submit Button\").exists())\n\tdef test_text_exists_submit_button_lower_case(self):\n\t\tself.assertTrue(Text(\"submit button\").exists())\n\tdef test_text_exists_link_with_title(self):\n\t\tself.assertTrue(Text(\"Link with title\").exists())\n\tdef test_text_exists_link_with_title_lower_case(self):\n\t\tself.assertTrue(Text(\"link with title\").exists())\n\tdef test_text_with_leading_nbsp_exists(self):\n\t\tself.assertTrue(Text(\"Text with leading &nbsp;\").exists())\n\tdef test_read_text_value(self):\n\t\tself.assertEqual(Text(to_right_of=Text(\"EUR/USD\")).value, \"1.3487\")\n\tdef test_free_text_not_surrounded_by_tags_exists(self):\n\t\tself.assertTrue(Text(\"Free text not surrounded by tags\").exists())\n\tdef test_text_with_apostrophe(self):\n\t\tself.assertTrue(Text(\"Your email's been sent!\").exists())\n\tdef test_text_with_double_quotes(self):\n\t\tself.assertTrue(Text('He said \"double quotes\".').exists())\n\tdef test_text_with_single_and_double_quotes(self):\n\t\tself.assertTrue(Text(\"Single'quote. Double\\\"quote.\").exists())\n\tdef test_text_uppercase_umlaut(self):\n\t\tself.assertTrue(Text('VERÖFFENTLICHEN').exists())\n\n\t# Link tests:\n\tdef test_link_exists(self):\n\t\tself.assertTrue(Link(\"Link\").exists())\n\tdef test_link_with_title_exists(self):\n\t\tself.assertTrue(Link('Link with title').exists())\n\tdef test_link_no_text(self):\n\t\tself.assertEqual(4, len(find_all(Link())))\n\tdef test_span_with_role_link_exists_as_link(self):\n\t\tself.assertTrue(Link(\"Span with role=link\").exists())\n\tdef test_link_href(self):\n\t\tself.assertEqual(Link(\"heliumhq.com\").href, \"http://heliumhq.com/\")\n\tdef test_link_empty_href(self):\n\t\tself.assertEqual(Link(\"Link with empty href\").href, \"\")\n\n\t# ListItem tests:\n\tdef test_list_item_no_text(self):\n\t\tall_list_items = find_all(ListItem(below=\"HTML Unordered List\"))\n\t\ttexts = {list_item.web_element.text for list_item in all_list_items}\n\t\tself.assertEqual({'ListItem 1', 'ListItem 2'}, texts)\n\n\t# Image tests:\n\tdef test_image_not_exists(self):\n\t\tself.assertFalse(Image(\"Non-existent\").exists())\n\tdef test_image_exists(self):\n\t\tself.assertTrue(Image(\"Dolphin\").exists())\n\n\t# Misc tests:\n\tdef test_text_field_combo_box_with_same_name(self):\n\t\ttext_field = TextField(\"Language\")\n\t\tcombo_box = ComboBox(\"Language\")\n\t\tself.assertNotEqual(text_field.y, combo_box.y)"
  },
  {
    "path": "tests/api/test_highlight.py",
    "content": "from helium import highlight, Button, Text, Config\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom tests.api import BrowserAT\n\nclass HighlightTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_gui_elements.html'\n\tdef test_highlight(self):\n\t\tbutton = Button(\"Input Button\")\n\t\thighlight(button)\n\t\tself._check_is_highlighted(button)\n\tdef test_highlight_string(self):\n\t\thighlight(\"Text with id\")\n\t\tself._check_is_highlighted(Text(\"Text with id\"))\n\tdef test_highlight_nonexistent(self):\n\t\twith TemporaryAttrValue(Config, 'implicit_wait_secs', .5):\n\t\t\twith self.assertRaises(LookupError):\n\t\t\t\thighlight(Button(\"foo\"))\n\tdef _check_is_highlighted(self, html_element):\n\t\tstyle = html_element.web_element.get_attribute(\"style\")\n\t\tself.assertTrue(\"border: 2px solid red;\" in style, style)\n\t\tself.assertTrue(\"font-weight: bold;\" in style, style)"
  },
  {
    "path": "tests/api/test_hover.py",
    "content": "from helium import hover, Config\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom helium._impl.util.system import is_windows\nfrom tests.api import BrowserAT\n\nclass HoverTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_hover.html'\n\tdef setUp(self):\n\t\t# This test fails if the mouse cursor happens to be over one of the\n\t\t# links in test_hover.html. Move the mouse cursor to (0, 0) to\n\t\t# prevent spurious test failures:\n\t\tself._move_mouse_cursor_to_origin()\n\t\tsuper().setUp()\n\tdef _move_mouse_cursor_to_origin(self):\n\t\tif is_windows():\n\t\t\tfrom win32api import SetCursorPos\n\t\t\tSetCursorPos((0, 0))\n\t\t# Feel free to add implementation for OSX/Linux here...\n\tdef test_hover_one(self):\n\t\thover('Dropdown 1')\n\t\tresult = self.read_result_from_browser()\n\t\tself.assertEqual(\n\t\t\t'Dropdown 1', result,\n\t\t\t\"Got unexpected result %r. Maybe the mouse cursor was over the \"\n\t\t\t\"browser window and interfered with the test?\" % result\n\t\t)\n\tdef test_hover_two_consecutively(self):\n\t\thover('Dropdown 2')\n\t\thover('Item C')\n\t\tresult = self.read_result_from_browser()\n\t\tself.assertEqual(\n\t\t\t'Dropdown 2 - Item C', result,\n\t\t\t\"Got unexpected result %r. Maybe the mouse cursor was over the \"\n\t\t\t\"browser window and interfered with the test?\" % result\n\t\t)\n\tdef test_hover_hidden(self):\n\t\twith TemporaryAttrValue(Config, 'implicit_wait_secs', 1):\n\t\t\ttry:\n\t\t\t\thover(\"Item C\")\n\t\t\texcept LookupError:\n\t\t\t\tpass # Success!\n\t\t\telse:\n\t\t\t\tself.fail(\n\t\t\t\t\t\"Didn't receive expected LookupError. Maybe the mouse \"\n\t\t\t\t\t\"cursor was over the browser window and interfered with \"\n\t\t\t\t\t\"the test?\"\n\t\t\t\t)"
  },
  {
    "path": "tests/api/test_iframe.py",
    "content": "from helium import Text, get_driver, find_all\nfrom tests.api import BrowserAT\n\nclass IframeTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn \"test_iframe/main.html\"\n\tdef test_test_text_in_iframe_exists(self):\n\t\tself.assertTrue(Text(\"This text is inside an iframe.\").exists())\n\tdef test_text_in_nested_iframe_exists(self):\n\t\tself.assertTrue(Text(\"This text is inside a nested iframe.\").exists())\n\tdef test_finds_element_in_parent_iframe(self):\n\t\tself.test_text_in_nested_iframe_exists()\n\t\t# Now we're \"focused\" on the nested IFrame. Check that we can still\n\t\t# find the element an the parent IFrame:\n\t\tself.test_test_text_in_iframe_exists()\n\tdef test_access_attributes_across_iframes(self):\n\t\ttext = Text(\"This text is inside an iframe.\")\n\t\tself.assertEqual(\"This text is inside an iframe.\", text.value)\n\t\tget_driver().switch_to.default_content()\n\t\tself.assertEqual(\"This text is inside an iframe.\", text.value)\n\tdef test_repr(self):\n\t\ttext, = find_all(Text(\"This text is inside an iframe.\"))\n\t\tget_driver().switch_to.default_content()\n\t\t# The text is now outside the current iframe. `repr(...)` should still\n\t\t# work without producing any errors:\n\t\tself.assertEqual(\"Text('This text is inside an iframe.')\", repr(text))"
  },
  {
    "path": "tests/api/test_implicit_wait.py",
    "content": "from helium import click, Config\nfrom helium._impl.util.lang import TemporaryAttrValue\nfrom tests.api import BrowserAT\nfrom time import time\n\nclass ImplicitWaitTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_implicit_wait.html'\n\tdef test_click_text_implicit_wait(self):\n\t\tclick(\"Click me!\")\n\t\tstart_time = time()\n\t\tclick(\"Now click me!\")\n\t\tend_time = time()\n\t\tself.assertEqual('Success!', self.read_result_from_browser())\n\t\tself.assertGreaterEqual(end_time - start_time, 3.0)\n\tdef test_click_text_no_implicit_wait(self):\n\t\twith TemporaryAttrValue(Config, 'implicit_wait_secs', 0):\n\t\t\twith self.assertRaises(LookupError):\n\t\t\t\tclick(\"Non-existent\")\n\tdef test_click_text_too_small_implicit_wait_secs(self):\n\t\twith TemporaryAttrValue(Config, 'implicit_wait_secs', 1):\n\t\t\tclick(\"Click me!\")\n\t\t\twith self.assertRaises(LookupError):\n\t\t\t\tclick(\"Now click me!\")"
  },
  {
    "path": "tests/api/test_kill_service_at_exit.py",
    "content": "from psutil import NoSuchProcess\n\nimport psutil\n\nclass KillServiceAtExitAT:\n\tdef test_kill_service_at_exit(self):\n\t\tself.start_browser_in_sub_process()\n\t\tself.assertEqual([], self.get_new_running_services())\n\tdef start_browser_in_sub_process(self):\n\t\traise NotImplementedError()\n\tdef get_new_running_services(self):\n\t\treturn [s for s in self.get_running_services()\n\t\t\t\tif s not in self.running_services_before]\n\tdef setUp(self):\n\t\tself.running_services_before = self.get_running_services()\n\t\tself.running_browsers_before = self.get_running_browsers()\n\tdef tearDown(self):\n\t\tfor service in self.get_new_running_services():\n\t\t\ttry:\n\t\t\t\tservice.terminate()\n\t\t\texcept NoSuchProcess:\n\t\t\t\t# Process terminated already.\n\t\t\t\tpass\n\t\tfor browser in self.get_new_running_browsers():\n\t\t\ttry:\n\t\t\t\tbrowser.terminate()\n\t\t\texcept NoSuchProcess:\n\t\t\t\t# Process terminated already.\n\t\t\t\tpass\n\tdef get_new_running_browsers(self):\n\t\treturn [s for s in self.get_running_browsers()\n\t\t\t\tif s not in self.running_browsers_before]\n\tdef get_running_services(self):\n\t\treturn self._get_running_processes(self.get_service_process_names())\n\tdef get_running_browsers(self):\n\t\treturn self._get_running_processes([self.get_browser_process_name()])\n\tdef _get_running_processes(self, image_names):\n\t\tresult = []\n\t\tfor p in psutil.process_iter():\n\t\t\tif p.name in image_names:\n\t\t\t\tresult.append(p)\n\t\treturn result\n\tdef get_service_process_names(self):\n\t\traise NotImplementedError()\n\tdef get_browser_process_name(self):\n\t\traise NotImplementedError()\n\tdef start_browser(self):\n\t\traise NotImplementedError()"
  },
  {
    "path": "tests/api/test_kill_service_at_exit_chrome.py",
    "content": "from helium import start_chrome\nfrom helium._impl.util.system import is_windows\nfrom tests.api import test_browser_name\nfrom tests.api.test_kill_service_at_exit import KillServiceAtExitAT\nfrom tests.api.util import InSubProcess\nfrom unittest import TestCase, skipIf\n\n@skipIf(test_browser_name() != 'chrome', 'Only run this test for Chrome')\nclass KillServiceAtExitChromeTest(KillServiceAtExitAT, TestCase):\n\tdef get_service_process_names(self):\n\t\tif is_windows():\n\t\t\treturn ['chromedriver.exe']\n\t\treturn ['chromedriver']\n\tdef get_browser_process_name(self):\n\t\treturn 'chrome' + ('.exe' if is_windows() else '')\n\tdef start_browser_in_sub_process(self):\n\t\twith ChromeInSubProcess():\n\t\t\tpass\n\nclass ChromeInSubProcess(InSubProcess):\n\t@classmethod\n\tdef main(cls):\n\t\tstart_chrome(headless=True)\n\t\tcls.synchronize_with_parent_process()\n\nif __name__ == '__main__':\n\tChromeInSubProcess.main()"
  },
  {
    "path": "tests/api/test_leaked_password.py",
    "content": "from helium import write, click, Text, wait_until\nfrom tests.api import BrowserAT\n\nclass LeakedPasswordTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_leaked_password.html'\n\tdef test_submit_leaked_password(self):\n\t\t# Chrome 140.0.7339.185 or earlier introduced password leak detection.\n\t\t# Writing leaked credentials into an input field sometimes brings up a\n\t\t# browser notification \"The password you just used was found in a data\n\t\t# breach\". Test that Helium prevents this:\n\t\twrite('testuser', into='Username')\n\t\twrite('testpassword', into='Password')\n\t\tclick('Submit')\n\t\twait_until(Text('You logged in with testuser:testpassword').exists)"
  },
  {
    "path": "tests/api/test_no_driver.py",
    "content": "from helium import *\nfrom helium._impl import APIImpl\nfrom unittest import TestCase\n\nclass NoDriverTest(TestCase):\n\tdef test_go_to_requires_driver(self):\n\t\tself._check_requires_driver(lambda: go_to('google.com'))\n\tdef test_write_requires_driver(self):\n\t\tself._check_requires_driver(lambda: write('foo'))\n\tdef test_press_requires_driver(self):\n\t\tself._check_requires_driver(lambda: press(ENTER))\n\tdef test_click_requires_driver(self):\n\t\tself._check_requires_driver(lambda: click(\"Sign in\"))\n\tdef test_doubleclick_requires_driver(self):\n\t\tself._check_requires_driver(lambda: doubleclick(\"Sign in\"))\n\tdef test_drag_requires_driver(self):\n\t\tself._check_requires_driver(lambda: drag(\"Drag me\", to=\"Drop here\"))\n\tdef test_find_all_requires_driver(self):\n\t\tself._check_requires_driver(lambda: find_all(Button()))\n\tdef test_scroll_down_requires_driver(self):\n\t\tself._check_requires_driver(lambda: scroll_down())\n\tdef test_scroll_up_requires_driver(self):\n\t\tself._check_requires_driver(lambda: scroll_up())\n\tdef test_scroll_right_requires_driver(self):\n\t\tself._check_requires_driver(lambda: scroll_right())\n\tdef test_scroll_left_requires_driver(self):\n\t\tself._check_requires_driver(lambda: scroll_left())\n\tdef test_hover_requires_driver(self):\n\t\tself._check_requires_driver(lambda: hover(\"Hi there!\"))\n\tdef test_rightclick_requires_driver(self):\n\t\tself._check_requires_driver(lambda: rightclick(\"Hi there!\"))\n\tdef test_select_requires_driver(self):\n\t\tself._check_requires_driver(lambda: select(\"Language\", \"English\"))\n\tdef test_drag_file_requires_driver(self):\n\t\tself._check_requires_driver(\n\t\t\tlambda: drag_file(r'C:\\test.txt', to=\"Here\")\n\t\t)\n\tdef test_attach_file_requires_driver(self):\n\t\tself._check_requires_driver(lambda: attach_file(r'C:\\test.txt'))\n\tdef test_refresh_requires_driver(self):\n\t\tself._check_requires_driver(lambda: refresh())\n\tdef test_wait_until_requires_driver(self):\n\t\tself._check_requires_driver(lambda: wait_until(lambda: True))\n\tdef test_switch_to_requires_driver(self):\n\t\tself._check_requires_driver(lambda: switch_to('Popup'))\n\tdef test_kill_browser_requires_driver(self):\n\t\tself._check_requires_driver(lambda: switch_to('Popup'))\n\tdef test_highlight_requires_driver(self):\n\t\tself._check_requires_driver(lambda: switch_to('Popup'))\n\tdef test_s_requires_driver(self):\n\t\tself._check_requires_driver(lambda: S('#home'))\n\tdef test_text_requires_driver(self):\n\t\tself._check_requires_driver(lambda: Text('Home'))\n\tdef test_link_requires_driver(self):\n\t\tself._check_requires_driver(lambda: Link('Home'))\n\tdef test_list_item_requires_driver(self):\n\t\tself._check_requires_driver(lambda: ListItem('Home'))\n\tdef test_button_requires_driver(self):\n\t\tself._check_requires_driver(lambda: Button('Home'))\n\tdef test_image_requires_driver(self):\n\t\tself._check_requires_driver(lambda: Image('Logo'))\n\tdef test_text_field_requires_driver(self):\n\t\tself._check_requires_driver(lambda: TextField('File name'))\n\tdef test_combo_box_requires_driver(self):\n\t\tself._check_requires_driver(lambda: ComboBox('Language'))\n\tdef test_check_box_requires_driver(self):\n\t\tself._check_requires_driver(lambda: CheckBox('True?'))\n\tdef test_radio_button_requires_driver(self):\n\t\tself._check_requires_driver(lambda: RadioButton('Option A'))\n\tdef test_window_requires_driver(self):\n\t\tself._check_requires_driver(lambda: Window('Main'))\n\tdef test_alert_requires_driver(self):\n\t\tself._check_requires_driver(lambda: Alert())\n\tdef _check_requires_driver(self, function):\n\t\twith self.assertRaises(RuntimeError) as cm:\n\t\t\tfunction()\n\t\tself.assertEqual(APIImpl.DRIVER_REQUIRED_MESSAGE, cm.exception.args[0])"
  },
  {
    "path": "tests/api/test_point.py",
    "content": "from helium import click, Point, Button, hover, rightclick, doubleclick, drag\nfrom tests.api import BrowserAT, test_browser_name\nfrom re import search\n\nclass PointTest(BrowserAT):\n\t\"\"\"\n\tTests helium.Point.\n\n\tThe tests allow for a coordinate difference between browsers of up to +/- 1\n\tpixel. For instance: In Firefox, Button(\"Button 1\").center is (39, 12), in\n\tIE and Chrome it is (39, 13). This really is because Firefox lays out the\n\tpage slightly differently, so that the button is further up on the page.\n\t\"\"\"\n\tdef get_page(self):\n\t\treturn 'test_point.html'\n\tdef setUp(self):\n\t\tsuper().setUp()\n\t\tif test_browser_name() != 'chrome':\n\t\t\t# Imagine two consecutive tests that hover the mouse cursor to the\n\t\t\t# same position. In the second test, Firefox does not generate a\n\t\t\t# \"mouse move\" event (probably because the cursor is already in the\n\t\t\t# \"correct\" location). However, this prevents our JavaScript from\n\t\t\t# firing, which updates the status text element required for this\n\t\t\t# text. Fix this by re-setting the mouse cursor. Chrome does not\n\t\t\t# suffer from this problem.\n\t\t\thover(Point(0, 0))\n\tdef test_top_left(self):\n\t\tself.assert_is_in_range(\n\t\t\tPoint(2, 3), Button(\"Button 1\").top_left, delta=(0, 1)\n\t\t)\n\tdef assert_is_in_range(self, expected, point, delta):\n\t\tx, y = point\n\t\texpected_x, expected_y = expected\n\t\tdelta_x, delta_y = delta\n\t\tself.assert_around(expected_x, x, delta_x)\n\t\tself.assert_around(expected_y, y, delta_y)\n\tdef assert_around(self, expected, actual, delta, msg=None):\n\t\tself.assertIn(\n\t\t\tactual, list(range(expected - delta, expected + delta + 1)), msg\n\t\t)\n\tdef test_click_top_left(self):\n\t\tclick(Button(\"Button 1\").top_left)\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 clicked at offset (0, 0).\", offset_delta=(1, 1)\n\t\t)\n\tdef test_click_point(self):\n\t\tclick(Point(39, 13))\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 clicked at offset (37, 10).\", offset_delta=(0, 1)\n\t\t)\n\tdef test_click_top_left_offset(self):\n\t\tclick(Button(\"Button 3\").top_left + (3, 4))\n\t\tself.assert_result_is(\"Button 3 clicked at offset (3, 4).\")\n\tdef test_hover_top_left(self):\n\t\thover(Button(\"Button 1\").top_left)\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 hovered at offset (0, 0).\", offset_delta=(1, 1)\n\t\t)\n\tdef test_hover_point(self):\n\t\thover(Point(39, 13))\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 hovered at offset (37, 10).\", offset_delta=(0, 1)\n\t\t)\n\tdef test_hover_top_left_offset(self):\n\t\thover(Button(\"Button 3\").top_left + (3, 4))\n\t\tself.assert_result_is(\"Button 3 hovered at offset (3, 4).\")\n\tdef test_rightclick_top_left(self):\n\t\trightclick(Button(\"Button 1\").top_left)\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 rightclicked at offset (0, 0).\", offset_delta=(1, 1)\n\t\t)\n\tdef test_rightclick_point(self):\n\t\trightclick(Point(39, 13))\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 rightclicked at offset (37, 10).\", offset_delta=(0, 1)\n\t\t)\n\tdef test_rightclick_top_left_offset(self):\n\t\trightclick(Button(\"Button 3\").top_left + (3, 4))\n\t\tself.assert_result_is(\n\t\t\t\"Button 3 rightclicked at offset (3, 4).\"\n\t\t)\n\tdef test_doubleclick_top_left(self):\n\t\tdoubleclick(Button(\"Button 1\").top_left)\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 doubleclicked at offset (0, 0).\", offset_delta=(1, 1)\n\t\t)\n\tdef test_doubleclick_point(self):\n\t\tdoubleclick(Point(39, 13))\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 doubleclicked at offset (37, 10).\", offset_delta=(0, 1)\n\t\t)\n\tdef test_doubleclick_top_left_offset(self):\n\t\tdoubleclick(Button(\"Button 3\").top_left + (3, 4))\n\t\tself.assert_result_is(\"Button 3 doubleclicked at offset (3, 4).\")\n\tdef test_drag_point(self):\n\t\tdrag(Button(\"Button 1\").top_left, to=Point(39, 13))\n\t\tself.assert_result_is(\n\t\t\t\"Button 1 clicked at offset (37, 10).\", offset_delta=(0, 1)\n\t\t)\n\tdef assert_result_is(self, expected, offset_delta=(0, 0)):\n\t\tactual = self.read_result_from_browser()\n\t\texpected_offset = self._extract_offset(expected)\n\t\tactual_offset = self._extract_offset(actual)\n\t\texpected_x, expected_y = eval(expected_offset)\n\t\tactual_x, actual_y = eval(actual_offset)\n\t\tdelta_x, delta_y = offset_delta\n\t\tself.assert_around(\n\t\t\texpected_x, actual_x, delta_x,\n\t\t\t\"Offset (%r, %r) is not in expected range (%r+-%r, %r+-%r).\" % (\n\t\t\t\tactual_x, actual_y, expected_x, delta_x, expected_y, delta_y\n\t\t\t)\n\t\t)\n\t\tself.assert_around(\n\t\t\texpected_x, actual_x, delta_x,\n\t\t\t\"Offset (%r, %r) is not in expected range (%r+-%r, %r+-%r).\" % (\n\t\t\t\tactual_x, actual_y, expected_x, delta_x, expected_y, delta_y\n\t\t\t)\n\t\t)\n\t\texpected_prefix, expected_suffix = expected.split(expected_offset)\n\t\tactual_prefix, actual_suffix = actual.split(actual_offset)\n\t\tself.assertEqual(expected_prefix, actual_prefix)\n\t\tself.assertEqual(expected_suffix, actual_suffix)\n\tdef _extract_offset(self, result_in_browser):\n\t\treturn search(r\"(\\([^,]+, [^\\)]+\\))\", result_in_browser).group(1)"
  },
  {
    "path": "tests/api/test_press.py",
    "content": "from helium import press, TextField, SHIFT\nfrom tests.api import BrowserAT\n\nclass PressTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_write.html'\n\tdef test_press_single_character(self):\n\t\tpress('a')\n\t\tself.assertEqual('a', TextField('Autofocus text field').value)\n\tdef test_press_upper_case_character(self):\n\t\tpress('A')\n\t\tself.assertEqual('A', TextField('Autofocus text field').value)\n\tdef test_press_shift_plus_lower_case_character(self):\n\t\tpress(SHIFT + 'a')\n\t\tself.assertEqual('A', TextField('Autofocus text field').value)"
  },
  {
    "path": "tests/api/test_repr.py",
    "content": "from helium import *\nfrom helium import HTMLElement\nfrom tests.api import BrowserAT\n\nimport re\n\nclass UnboundReprTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_gui_elements.html'\n\tdef test_unbound_s_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"S('.cssClass')\", repr(S('.cssClass'))\n\t\t)\n\tdef test_unbound_s_repr_below(self):\n\t\tself.assertEqual(\n\t\t\t\"S('.cssClass', below='Home')\", repr(S('.cssClass', below='Home'))\n\t\t)\n\tdef test_unbound_text_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"Text('Hello World!')\", repr(Text('Hello World!'))\n\t\t)\n\tdef test_unbound_link_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"Link('Download')\", repr(Link('Download'))\n\t\t)\n\tdef test_unbound_list_item_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"ListItem('Home')\", repr(ListItem('Home'))\n\t\t)\n\tdef test_unbound_button_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"Button('Home')\", repr(Button('Home'))\n\t\t)\n\tdef test_unbound_image_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"Image('Logo')\", repr(Image('Logo'))\n\t\t)\n\tdef test_unbound_text_field_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"TextField('File name')\", repr(TextField('File name'))\n\t\t)\n\tdef test_unbound_combo_box_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"ComboBox('Language')\", repr(ComboBox('Language'))\n\t\t)\n\tdef test_unbound_check_box_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"CheckBox('True?')\", repr(CheckBox('True?'))\n\t\t)\n\tdef test_unbound_radio_button_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"RadioButton('Option A')\", repr(RadioButton('Option A'))\n\t\t)\n\tdef test_unbound_window_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"Window('Main')\", repr(Window('Main'))\n\t\t)\n\tdef test_unbound_alert_repr(self):\n\t\tself.assertEqual(\n\t\t\t\"Alert()\", repr(Alert())\n\t\t)\n\tdef test_unbound_alert_repr_with_search_text(self):\n\t\tself.assertEqual(\n\t\t\t\"Alert('Hello World')\", repr(Alert('Hello World'))\n\t\t)\n\nclass BoundReprTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_gui_elements.html'\n\tdef test_bound_s_repr(self):\n\t\tbound_s = self._bind(S(\"#checkBoxId\"))\n\t\tself._assertHtmlEltWithMultipleAttributesEquals(\n\t\t\t'<input type=\"checkbox\" id=\"checkBoxId\" name=\"checkBoxName\" '\n\t\t\t'class=\"checkBoxClass\">',\n\t\t\trepr(bound_s)\n\t\t)\n\tdef test_bound_s_repr_long_content(self):\n\t\tbody = self._bind(S(\"body\"))\n\t\tself.assertEqual(\"<body>...</body>\", repr(body))\n\tdef test_bound_button_repr(self):\n\t\tbound_button = self._bind(Button('Enabled Button'))\n\t\tself.assertEqual(\n\t\t\t'<button type=\"button\">Enabled Button</button>', repr(bound_button)\n\t\t)\n\tdef test_bound_link_repr_nested_tag(self):\n\t\tlink = self._bind(Link(\"Link with title\"))\n\t\tself._assertHtmlEltWithMultipleAttributesEquals(\n\t\t\t'<a href=\"#\" title=\"Link with title\">...</a>', repr(link)\n\t\t)\n\tdef test_bound_repr_duplicate_button(self):\n\t\tself.assertEqual(\n\t\t\t'[<button type=\"button\">Duplicate Button</button>,'\n\t\t\t' <button type=\"button\">Duplicate Button</button>,'\n\t\t\t' <button type=\"button\">Duplicate Button</button>,'\n\t\t\t' <button type=\"button\">Duplicate Button</button>]',\n\t\t\trepr(find_all(Button(\"Duplicate Button\")))\n\t\t)\n\tdef test_bound_window_repr(self):\n\t\tbound_window = self._bind(Window())\n\t\tself.assertEqual(\n\t\t\t\"Window('Test page for browser system tests')\", repr(bound_window)\n\t\t)\n\tdef test_bound_window_repr_with_search_text(self):\n\t\tbound_window = self._bind(Window('Test page for'))\n\t\tself.assertEqual(\n\t\t\t\"Window('Test page for browser system tests')\", repr(bound_window)\n\t\t)\n\tdef _bind(self, predicate):\n\t\tif isinstance(predicate, HTMLElement):\n\t\t\t# Reading a property such as web_element waits for the element to\n\t\t\t# exist and binds the predicate to it:\n\t\t\tpredicate.web_element\n\t\telse:\n\t\t\tassert isinstance(predicate, Window)\n\t\t\t# Reading a property such as handle waits for the element to\n\t\t\t# exist and binds the predicate to it:\n\t\t\tpredicate.handle\n\t\treturn predicate\n\tdef _assertHtmlEltWithMultipleAttributesEquals(self, expected, actual):\n\t\tstart_tag_exp, remainder_exp = expected.split('>', 1)\n\t\tstart_tag_act, remainder_act = actual.split('>', 1)\n\t\tattributes_re = '[a-zA-Z]+=\"[^\"]+\"'\n\t\tattributes_exp = re.findall(attributes_re, start_tag_exp)\n\t\tattributes_act = re.findall(attributes_re, start_tag_act)\n\t\tself.assertEqual(set(attributes_exp), set(attributes_act))\n\t\tself.assertEqual(remainder_exp, remainder_act)\n\nclass BoundAlertReprTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_alert.html'\n\tdef setUp(self):\n\t\tsuper().setUp()\n\t\tclick(\"Display alert\")\n\tdef test_bound_alert_repr(self):\n\t\talert = Alert()\n\t\t# Bind alert:\n\t\talert.text\n\t\tself.assertEqual(\"Alert('Hello World!')\", repr(alert))\n\tdef test_bound_alert_repr_with_partial_search_text(self):\n\t\talert = Alert('Hello')\n\t\t# Bind alert:\n\t\talert.text\n\t\tself.assertEqual(\"Alert('Hello World!')\", repr(alert))\n\tdef tearDown(self):\n\t\tAlert().accept()\n\t\tsuper().tearDown()"
  },
  {
    "path": "tests/api/test_rightclick.py",
    "content": "from helium import click, rightclick\nfrom tests.api import BrowserAT\n\nclass RightclickTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_rightclick.html'\n\tdef test_simple_rightclick(self):\n\t\trightclick(\"Perform a normal rightclick here.\")\n\t\tself.assertEqual(\n\t\t\t\"Normal rightclick performed.\", self.read_result_from_browser()\n\t\t)\n\tdef test_rightclick_select_normal_item(self):\n\t\trightclick(\"Rightclick here for context menu.\")\n\t\tclick(\"Normal item\")\n\t\tself.assertEqual(\n\t\t\t\"Normal item selected.\", self.read_result_from_browser()\n\t\t)"
  },
  {
    "path": "tests/api/test_s.py",
    "content": "from helium import S\nfrom tests.api import BrowserAT\n\nclass STest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_gui_elements.html'\n\tdef test_find_by_id(self):\n\t\tself.assertFindsEltWithId(S(\"#checkBoxId\"), 'checkBoxId')\n\tdef test_find_by_name(self):\n\t\tself.assertFindsEltWithId(S(\"@checkBoxName\"), 'checkBoxId')\n\tdef test_find_by_class(self):\n\t\tself.assertFindsEltWithId(S(\".checkBoxClass\"), 'checkBoxId')\n\tdef test_find_by_xpath(self):\n\t\tself.assertFindsEltWithId(\n\t\t\tS(\"//input[@type='checkbox' and @id='checkBoxId']\"), 'checkBoxId'\n\t\t)\n\tdef test_find_by_css_selector(self):\n\t\tself.assertFindsEltWithId(S('input.checkBoxClass'), 'checkBoxId')"
  },
  {
    "path": "tests/api/test_scroll.py",
    "content": "from helium import scroll_down, scroll_left, scroll_right, scroll_up\nfrom tests.api import BrowserAT\n\nclass ScrollTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_scroll.html'\n\tdef test_scroll_up_when_at_top_of_page(self):\n\t\tscroll_up()\n\t\tself.assert_scroll_position_equals(0, 0)\n\tdef test_scroll_down(self):\n\t\tscroll_down()\n\t\tself.assert_scroll_position_equals(0, 100)\n\tdef test_scroll_down_then_up(self):\n\t\tscroll_down()\n\t\tscroll_up()\n\t\tself.assert_scroll_position_equals(0, 0)\n\tdef test_scroll_down_then_up_pixels(self):\n\t\tscroll_down(175)\n\t\tscroll_up(100)\n\t\tself.assert_scroll_position_equals(0, 75)\n\tdef test_scroll_left_when_at_start_of_page(self):\n\t\tscroll_left()\n\t\tself.assert_scroll_position_equals(0, 0)\n\tdef test_scroll_right(self):\n\t\tscroll_right()\n\t\tself.assert_scroll_position_equals(100, 0)\n\tdef test_scroll_right_then_left(self):\n\t\tscroll_right()\n\t\tscroll_left()\n\t\tself.assert_scroll_position_equals(0, 0)\n\tdef test_scroll_right_then_left_pixels(self):\n\t\tscroll_right(175)\n\t\tscroll_left(100)\n\t\tself.assert_scroll_position_equals(75, 0)\n\tdef tearDown(self):\n\t\t# Recent versions of Chrome(Driver) don't reset the scroll position when\n\t\t# reloading the page. Force-reset it:\n\t\tself.driver.execute_script('window.scrollTo(0, 0);')\n\t\tsuper().tearDown()\n\tdef assert_scroll_position_equals(self, x, y):\n\t\tscroll_position_x = self.driver.execute_script(\n\t\t\t'return window.pageXOffset || document.documentElement.scrollLeft '\n\t\t\t'|| document.body.scrollLeft'\n\t\t)\n\t\tself.assertEqual(x, scroll_position_x)\n\t\tscroll_position_y = self.driver.execute_script(\n\t\t\t'return window.pageYOffset || document.documentElement.scrollTop '\n\t\t\t'|| document.body.scrollTop'\n\t\t)\n\t\tself.assertEqual(y, scroll_position_y)"
  },
  {
    "path": "tests/api/test_start_go_to.py",
    "content": "from helium import go_to\nfrom tests.api import start_browser\nfrom tests.api.util import get_data_file_url\nfrom os import path\nfrom unittest import TestCase\n\nclass StartGoToTest(TestCase):\n\tdef setUp(self):\n\t\tself.url = get_data_file_url('test_start_go_to.html')\n\t\tself.driver = None\n\tdef test_go_to(self):\n\t\tself.driver = start_browser()\n\t\tgo_to(self.url)\n\t\tself.assertUrlEquals(self.url, self.driver.current_url)\n\tdef assertUrlEquals(self, expected, actual):\n\t\texpected = str(path.normpath(expected.lower().replace('\\\\', '/')))\n\t\tactual = str(path.normpath(actual.lower().replace('\\\\', '/')))\n\t\tself.assertEqual(expected, actual)\n\tdef test_start_with_url(self):\n\t\tself.driver = start_browser(self.url)\n\t\tself.assertUrlEquals(self.url, self.driver.current_url)\n\tdef tearDown(self):\n\t\tif self.driver is not None:\n\t\t\tself.driver.quit()"
  },
  {
    "path": "tests/api/test_tables.py",
    "content": "from helium import *\nfrom tests.api import BrowserAT\n\nclass TablesTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_tables.html'\n\tdef test_s_below_above(self):\n\t\tsecond_table_cells = find_all(\n\t\t\tS(\"table > tbody > tr > td\",\n\t\t\t\tbelow=Text(\"Table no. 2\"),\n\t\t\t\tabove=Text(\"Table no. 3\")\n\t\t\t)\n\t\t)\n\t\tself.assertEqual(len(second_table_cells), 9)\n\t\tself.assertListEqual(\n\t\t\tsorted([cell.web_element.text for cell in second_table_cells]),\n\t\t    ['T2R1C1', 'T2R1C2', 'T2R1C3',\n\t\t     'T2R2C1', 'T2R2C2', 'T2R2C3',\n\t\t     'T2R3C1', 'T2R3C2', 'T2R3C3']\n\t\t)\n\tdef test_s_read_table_column(self):\n\t\temail_cells = find_all(S(\"table > tbody > tr > td\", below=\"Email\"))\n\t\tself.assertEqual(len(email_cells), 3)\n\t\tself.assertListEqual(\n\t\t\tsorted([cell.web_element.text for cell in email_cells]),\n\t\t    ['email1@domain.com', 'email2@domain.com', 'email3@domain.com']\n\t\t)\n\tdef test_text_below_to_left_of(self):\n\t\tself.assertEqual(\n\t\t\t'Abdul', Text(below='Name', to_left_of='email2@domain.com').value\n\t\t)"
  },
  {
    "path": "tests/api/test_text_impl.py",
    "content": "from helium._impl import TextImpl\nfrom helium._impl.selenium_wrappers import WebDriverWrapper\nfrom selenium.webdriver.common.by import By\nfrom tests.api import BrowserAT\n\nclass TextImplTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_text_impl.html'\n\tdef test_empty_search_text_xpath(self):\n\t\txpath = TextImpl(WebDriverWrapper(self.driver))._get_search_text_xpath()\n\t\ttext_elements = self.driver.find_elements(By.XPATH, xpath)\n\t\ttexts = [w.get_attribute('innerHTML') for w in text_elements]\n\t\tself.assertEqual(\n\t\t\t[\"A paragraph\", \"A paragraph inside a div\",\n\t\t\t \"Another paragraph inside the div\"],\n\t\t\tsorted(texts)\n\t\t)"
  },
  {
    "path": "tests/api/test_wait_until.py",
    "content": "from helium import click, wait_until, Text\nfrom tests.api import BrowserAT\nfrom selenium.common.exceptions import TimeoutException\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.support.expected_conditions import \\\n\tpresence_of_element_located\nfrom time import time\n\nclass WaitUntilTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_wait_until.html'\n\tdef test_wait_until_text_exists(self):\n\t\tclick(\"Click me!\")\n\t\tstart_time = time()\n\t\twait_until(Text(\"Success!\").exists)\n\t\tend_time = time()\n\t\tself.assertGreaterEqual(end_time - start_time, 0.8)\n\tdef test_wait_until_presence_of_element_located(self):\n\t\tclick(\"Click me!\")\n\t\tstart_time = time()\n\t\twait_until(presence_of_element_located((By.ID, \"result\")))\n\t\tend_time = time()\n\t\tself.assertGreaterEqual(end_time - start_time, 0.8)\n\tdef test_wait_until_lambda_expires(self):\n\t\twith self.assertRaises(TimeoutException):\n\t\t\twait_until(lambda: False, timeout_secs=1)\n\tdef test_wait_until_lambda_with_driver_expires(self):\n\t\twith self.assertRaises(TimeoutException):\n\t\t\twait_until(lambda driver: False, timeout_secs=0.1)\n"
  },
  {
    "path": "tests/api/test_window.py",
    "content": "from helium import Window, click, go_to, get_driver, wait_until\nfrom tests.api.util import get_data_file_url\nfrom tests.api import BrowserAT\n\nclass WindowTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_window/test_window.html'\n\tdef test_window_exists(self):\n\t\tself.assertTrue(Window('test_window').exists())\n\tdef test_window_not_exists(self):\n\t\tself.assertFalse(Window('non-existent').exists())\n\tdef test_no_arg_window_exists(self):\n\t\tself.assertTrue(Window().exists())\n\tdef test_handle(self):\n\t\tself.assertTrue(Window('test_window').handle)\n\tdef test_title(self):\n\t\tself.assertEqual('test_window', Window('test_window').title)\n\nclass MultipleWindowTest(WindowTest):\n\t\"\"\"\n\tThe purpose of this Test is to run the same tests as WindowTest, but with an\n\tadditional pop up window open.\n\t\"\"\"\n\t@classmethod\n\tdef setUpClass(cls):\n\t\tsuper().setUpClass()\n\t\tgo_to(get_data_file_url('test_window/test_window.html'))\n\t\tclick(\"Click here to open a popup.\")\n\t\twait_until(Window('test_window - popup').exists)\n\tdef test_popup_window_exists(self):\n\t\tself.assertTrue(Window('test_window - popup').exists())\n\tdef setUp(self):\n\t\t# Don't let super go_to(...):\n\t\tpass\n\t@classmethod\n\tdef tearDownClass(cls):\n\t\tpopup_window_handle = Window(\"test_window - popup\").handle\n\t\tmain_window_handle = Window(\"test_window\").handle\n\t\tget_driver().switch_to.window(popup_window_handle)\n\t\tget_driver().close()\n\t\tget_driver().switch_to.window(main_window_handle)\n\t\tsuper().tearDownClass()"
  },
  {
    "path": "tests/api/test_window_handling.py",
    "content": "from helium import write, click, switch_to, TextField, Text, get_driver, \\\n\tLink, wait_until\nfrom selenium.webdriver.common.by import By\nfrom tests.api import BrowserAT, test_browser_name\nfrom unittest import skipIf\n\nclass WindowHandlingTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_window_handling/main.html'\n\tdef test_write_writes_in_active_window(self):\n\t\twrite(\"Main window\")\n\t\tself.assertEqual(\"Main window\", self._get_value('mainTextField'))\n\t\tself._open_popup()\n\t\twrite(\"Popup\")\n\t\tself.assertEqual(\"Popup\", self._get_value('popupTextField'))\n\tdef test_write_searches_in_active_window(self):\n\t\twrite(\"Main window\", into=\"Text field\")\n\t\tself.assertEqual(\"Main window\", self._get_value('mainTextField'))\n\t\tself._open_popup()\n\t\twrite(\"Popup\", into=\"Text field\")\n\t\tself.assertEqual(\"Popup\", self._get_value('popupTextField'))\n\tdef test_switch_to_search_text_field(self):\n\t\twrite(\"Main window\", into=\"Text field\")\n\t\tself.assertEqual(\"Main window\", TextField(\"Text field\").value)\n\t\tself._open_popup()\n\t\twrite(\"Popup\", into=\"Text field\")\n\t\tself.assertEqual(\"Popup\", TextField(\"Text field\").value)\n\t\tswitch_to(\"test_window_handling - Main\")\n\t\tself.assertEqual(\"Main window\", TextField(\"Text field\").value)\n\tdef test_handles_closed_window_gracefully(self):\n\t\tself._open_popup()\n\t\tget_driver().close()\n\t\tis_back_in_main_window = Link(\"Open popup\").exists()\n\t\tself.assertTrue(is_back_in_main_window)\n\tdef test_switch_to_after_window_closed(self):\n\t\tself._open_popup()\n\t\tget_driver().close()\n\t\tswitch_to('test_window_handling - Main')\n\tdef setUp(self):\n\t\tsuper().setUp()\n\t\tself.main_window_handle = self.driver.current_window_handle\n\tdef tearDown(self):\n\t\tfor window_handle in self.driver.window_handles:\n\t\t\tif window_handle != self.main_window_handle:\n\t\t\t\tself.driver.switch_to.window(window_handle)\n\t\t\t\tself.driver.close()\n\t\tself.driver.switch_to.window(self.main_window_handle)\n\t\tsuper().tearDown()\n\tdef _get_value(self, element_id):\n\t\treturn self.driver.find_element(By.ID, element_id).get_attribute('value')\n\tdef _open_popup(self):\n\t\tclick(\"Open popup\")\n\t\twait_until(self._is_in_popup)\n\tdef _is_in_popup(self):\n\t\treturn get_driver().title == 'test_window_handling - Popup'\n\nclass WindowHandlingOnStartBrowserTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_window_handling/main_immediate_popup.html'\n\t@skipIf(test_browser_name() == 'firefox', 'This test fails on Firefox')\n\tdef test_switches_to_popup(self):\n\t\tself.assertTrue(Text(\"In popup.\").exists())"
  },
  {
    "path": "tests/api/test_write.py",
    "content": "from helium import write, TextField\nfrom tests.api import BrowserAT\n\nclass WriteTest(BrowserAT):\n\tdef get_page(self):\n\t\treturn 'test_write.html'\n\tdef test_write(self):\n\t\twrite('Hello World!')\n\t\tself.assertEqual(\n\t\t\t'Hello World!', TextField('Autofocus text field').value\n\t\t)\n\tdef test_write_into(self):\n\t\tvalue = 'Hi there!'\n\t\tlabel = 'Normal text field'\n\t\twrite(value, into=label)\n\t\tself.assertEqual(value, TextField(label).value)\n\tdef test_write_into_text_field_to_right_of(self):\n\t\tvalue = 'Hi there!'\n\t\tlabel = 'Normal text field'\n\t\twrite(value, into=TextField(to_right_of=label))\n\t\tself.assertEqual(value, TextField(label).value)\n\tdef test_write_into_input_type_date(self):\n\t\tlabel = 'Input type=date'\n\t\twrite('23.08.2024', into=TextField(to_right_of=label))\n\t\tself.assertEqual('2024-08-23', TextField(label).value)\n\tdef test_write_into_input_type_time(self):\n\t\tlabel = 'Input type=time'\n\t\twrite('1749', into=TextField(to_right_of=label))\n\t\tself.assertEqual('17:49', TextField(label).value)"
  },
  {
    "path": "tests/api/util.py",
    "content": "from os.path import dirname, join\nfrom pathlib import Path\nfrom subprocess import Popen, PIPE, STDOUT\n\nimport os\nimport sys\n\ndef get_data_file(*rel_path):\n\treturn join(dirname(__file__), 'data', *rel_path)\n\ndef get_data_file_url(data_file):\n\treturn Path(get_data_file(data_file)).as_uri()\n\nclass InSubProcess:\n\t\"\"\"\n\tImportant: You need to call `synchronize_with_parent_process()` in your sub-\n\tclass's `main` method.\n\t\"\"\"\n\tdef __init__(self):\n\t\tself.sub_process = None\n\tdef __enter__(self):\n\t\tself.sub_process = Popen(\n\t\t\t['python', '-m', self.__class__.__module__],\n\t\t\tstdin=PIPE, stdout=PIPE, stderr=STDOUT, universal_newlines=True,\n\t\t\tcwd=os.getcwd(), env=os.environ\n\t\t)\n\t\tself.sub_process.__enter__()\n\t\tself.wait_for_sub_process()\n\tdef wait_for_sub_process(self):\n\t\tline = self.sub_process.stdout.readline()\n\t\tassert 'Sub process started.\\n' == line, \\\n\t\t\t'Sub process invocation failed:\\n' + \\\n\t\t\tline + self.sub_process.stdout.read()\n\t@classmethod\n\tdef synchronize_with_parent_process(cls):\n\t\t# Let parent process know we've started:\n\t\tsys.stdout.write('Sub process started.\\n')\n\t\tsys.stdout.flush()\n\t\t# Wait until parent process is finished:\n\t\tinput('')\n\tdef __exit__(self, *args):\n\t\tself.sub_process.stdin.write('\\n')\n\t\tself.sub_process.stdin.flush()\n\t\tself.sub_process.__exit__(*args)\n\t\tself.sub_process.wait()\n\t\tassert self.sub_process.returncode == 0, \\\n\t\t\trepr(self.sub_process.returncode)"
  },
  {
    "path": "tests/unit/__init__.py",
    "content": ""
  },
  {
    "path": "tests/unit/test__impl/__init__.py",
    "content": ""
  },
  {
    "path": "tests/unit/test__impl/test_selenium_wrappers.py",
    "content": "from helium._impl.selenium_wrappers import FrameIterator, \\\n\tFramesChangedWhileIterating\nfrom selenium.common.exceptions import NoSuchFrameException\nfrom unittest import TestCase\n\nclass FrameIteratorTest(TestCase):\n\tdef test_only_main_frame(self):\n\t\tself.assertEqual([[]], list(FrameIterator(StubWebDriver())))\n\tdef test_one_frame(self):\n\t\tdriver = StubWebDriver(Frame())\n\t\tself.assertEqual([[], [0]], list(FrameIterator(driver)))\n\tdef test_two_frames(self):\n\t\tdriver = StubWebDriver(Frame(), Frame())\n\t\tself.assertEqual([[], [0], [1]], list(FrameIterator(driver)))\n\tdef test_nested_frame(self):\n\t\tdriver = StubWebDriver(Frame(Frame()))\n\t\tself.assertEqual([[], [0], [0, 0]], list(FrameIterator(driver)))\n\tdef test_complex(self):\n\t\tdriver = StubWebDriver(Frame(Frame()), Frame())\n\t\tself.assertEqual([[], [0], [0, 0], [1]], list(FrameIterator(driver)))\n\tdef test_disappearing_frame(self):\n\t\tchild_frame = Frame()\n\t\tfirst_frame = Frame(child_frame)\n\t\tdriver = StubWebDriver(first_frame)\n\t\t# We allow precisely 2 frame switches: One to first_frame and one to\n\t\t# child_frame. After this, FrameIterator tries to switch back to\n\t\t# first_frame, to see whether it has other children besides child_frame.\n\t\t# This is where we raise a NoSuchFrameException (by limiting the num.\n\t\t# of frame switches to 2). This simulates a situation where first_frame\n\t\t# disappears during iteration.\n\t\tdriver.switch_to = TargetLocatorFailingAfterNFrameSwitches(driver, 2)\n\t\twith self.assertRaises(FramesChangedWhileIterating):\n\t\t\tlist(FrameIterator(driver))\n\nclass StubWebDriver:\n\tdef __init__(self, *frames):\n\t\tself.frames = list(frames)\n\t\tself.switch_to = StubTargetLocator(self)\n\t\tself.current_frame = None\n\nclass StubTargetLocator:\n\tdef __init__(self, driver):\n\t\tself.driver = driver\n\tdef default_content(self):\n\t\tself.driver.current_frame = None\n\tdef frame(self, index):\n\t\tif self.driver.current_frame is None:\n\t\t\tchildren = self.driver.frames\n\t\telse:\n\t\t\tchildren = self.driver.current_frame.children\n\t\ttry:\n\t\t\tnew_frame = children[index]\n\t\texcept IndexError:\n\t\t\traise NoSuchFrameException()\n\t\telse:\n\t\t\tself.driver.current_frame = new_frame\n\nclass Frame:\n\tdef __init__(self, *children):\n\t\tself.children = children\n\nclass TargetLocatorFailingAfterNFrameSwitches(StubTargetLocator):\n\tdef __init__(self, driver, num_allowed_frame_switches):\n\t\tsuper(TargetLocatorFailingAfterNFrameSwitches, self).__init__(driver)\n\t\tself.num_allowed_frame_switches = num_allowed_frame_switches\n\tdef frame(self, index):\n\t\tif self.num_allowed_frame_switches > 0:\n\t\t\tself.num_allowed_frame_switches -= 1\n\t\t\treturn super(TargetLocatorFailingAfterNFrameSwitches, self)\\\n\t\t\t\t.frame(index)\n\t\traise NoSuchFrameException()"
  },
  {
    "path": "tests/unit/test__impl/test_util/__init__.py",
    "content": ""
  },
  {
    "path": "tests/unit/test__impl/test_util/test_dictionary.py",
    "content": "from helium._impl.util.dictionary import inverse\nfrom unittest import TestCase\n\nclass InverseTest(TestCase):\n\tdef test_inverse_empty(self):\n\t\tself.assertEqual({}, inverse({}))\n\tdef test_inverse(self):\n\t\tnames_for_ints = {\n\t\t\t0: {\"zero\", \"naught\"},\n\t\t\t1: {\"one\"}\n\t\t}\n\t\tints_for_names = {\n\t\t\t\"zero\": {0},\n\t\t\t\"naught\" : {0},\n\t\t\t\"one\": {1}\n\t\t}\n\t\tself.assertEqual(ints_for_names, inverse(names_for_ints))"
  },
  {
    "path": "tests/unit/test__impl/test_util/test_html.py",
    "content": "from unittest import TestCase\nfrom helium._impl.util.html import normalize_whitespace, \\\n\tget_easily_readable_snippet\n\nclass GetEasilyReadableSnippetTest(TestCase):\n\tdef test_no_tag(self):\n\t\tself.assertEqual(\n\t\t\t'Hello World!', get_easily_readable_snippet('Hello World!')\n\t\t)\n\tdef test_completely_empty_tag(self):\n\t\tself.assertEqual('<>', get_easily_readable_snippet('<>'))\n\tdef test_empty_tag_with_attributes(self):\n\t\tempty_tag_with_attrs = \\\n\t\t\t'<input type=\"checkbox\" id=\"checkBoxId\" name=\"checkBoxName\" ' \\\n\t\t\t'class=\"checkBoxClass\">'\n\t\tself.assertEqual(\n\t\t\tempty_tag_with_attrs,\n\t\t\tget_easily_readable_snippet(empty_tag_with_attrs)\n\t\t)\n\tdef test_tag_with_nested_tags(self):\n\t\tself.assertEqual(\n\t\t\t'<body>...</body>',\n\t\t\tget_easily_readable_snippet('<body><p>Hello World!</p></body>')\n\t\t)\n\tdef test_tag_with_long_content(self):\n\t\ttag_with_long_content = '<body>%s</body>' % ('x' * 100)\n\t\tself.assertEqual(\n\t\t\t'<body>...</body>',\n\t\t\tget_easily_readable_snippet(tag_with_long_content)\n\t\t)\n\nclass NormalizeWhitespaceTest(TestCase):\n\tdef test_string_without_whitespace(self):\n\t\tself.assertEqual('Foo', normalize_whitespace('Foo'))\n\tdef test_string_one_whitespace(self):\n\t\tself.assertEqual('Hello World!', normalize_whitespace('Hello World!'))\n\tdef test_string_leading_whitespace(self):\n\t\tself.assertEqual('Hello World!', normalize_whitespace(' Hello World!'))\n\tdef test_string_complex_whitespace(self):\n\t\tself.assertEqual(\n\t\t\t'Hello World!', normalize_whitespace('\\n\\t Hello\\t\\t    World!  \\n')\n\t\t)\n\tdef test_tag_with_spaces_around_inner_html(self):\n\t\tself.assertEqual(\n\t\t\t'<span>Hi there!</span>',\n\t\t\tnormalize_whitespace('<span> Hi there! </span>')\n\t\t)"
  },
  {
    "path": "tests/unit/test__impl/test_util/test_xpath.py",
    "content": "from helium._impl.util.xpath import predicate_or\nfrom unittest import TestCase\n\nclass PredicateOrTest(TestCase):\n\tdef test_no_args(self):\n\t\tself.assertEqual('', predicate_or())\n\tdef test_one_arg(self):\n\t\tself.assertEqual('[a=b]', predicate_or('a=b'))\n\tdef test_two_args(self):\n\t\tself.assertEqual('[a=b or c=d]', predicate_or('a=b', 'c=d'))\n\tdef test_one_empty_arg(self):\n\t\tself.assertEqual('', predicate_or(''))\n\tdef test_empty_arg_among_normal_args(self):\n\t\tself.assertEqual('[a=b or c=d]', predicate_or('a=b', '', 'c=d'))"
  }
]