├── .gitignore ├── .travis.yml ├── DESCRIPTION.rst ├── MANIFEST.in ├── NOTICE ├── README.rst ├── pylintrc ├── requirements.txt ├── scripts └── build-and-upload.sh ├── setup.cfg ├── setup.py └── src └── pytractor ├── __init__.py ├── exceptions.py ├── mixins.py ├── protractor ├── .gitignore ├── extract-clientsidescripts.coffee ├── extract-clientsidescripts.js └── extracted │ ├── LICENSE │ ├── allowAnimations.js │ ├── evaluate.js │ ├── findAllRepeaterRows.js │ ├── findBindings.js │ ├── findByButtonText.js │ ├── findByCssContainingText.js │ ├── findByModel.js │ ├── findByOptions.js │ ├── findByPartialButtonText.js │ ├── findRepeaterColumn.js │ ├── findRepeaterElement.js │ ├── findRepeaterRows.js │ ├── getLocationAbsUrl.js │ ├── getPendingHttpRequests.js │ ├── installInBrowser.js │ ├── setLocation.js │ ├── testForAngular.js │ └── waitForAngular.js ├── tests ├── __init__.py ├── functional │ ├── __init__.py │ ├── test_base.py │ ├── test_helpers.py │ ├── test_locators.py │ ├── test_wait.py │ ├── test_webdriver.py │ ├── testapp │ │ ├── LICENSE │ │ ├── alt_root_index.html │ │ ├── animation │ │ │ ├── animation.css │ │ │ ├── animation.html │ │ │ └── animation.js │ │ ├── app.css │ │ ├── app.js │ │ ├── async │ │ │ ├── async.html │ │ │ └── async.js │ │ ├── bindings │ │ │ ├── bindings.html │ │ │ └── bindings.js │ │ ├── components │ │ │ └── app-version.js │ │ ├── conflict │ │ │ ├── conflict.html │ │ │ └── conflict.js │ │ ├── favicon.ico │ │ ├── form │ │ │ ├── form.html │ │ │ └── form.js │ │ ├── index-no-angular.html │ │ ├── index.html │ │ ├── interaction │ │ │ ├── interaction.html │ │ │ └── interaction.js │ │ ├── lib │ │ │ ├── angular │ │ │ ├── angular_v1.2.9 │ │ │ │ ├── angular-animate.min.js │ │ │ │ ├── angular-animate.min.js.map │ │ │ │ ├── angular-route.min.js │ │ │ │ ├── angular-route.min.js.map │ │ │ │ ├── angular.min.js │ │ │ │ └── angular.min.js.map │ │ │ ├── angular_v1.3.13 │ │ │ │ ├── angular-animate.min.js │ │ │ │ ├── angular-animate.min.js.map │ │ │ │ ├── angular-aria.min.js │ │ │ │ ├── angular-route.min.js │ │ │ │ ├── angular-route.min.js.map │ │ │ │ ├── angular.min.js │ │ │ │ └── angular.min.js.map │ │ │ └── angular_version.js │ │ ├── login.html │ │ ├── polling │ │ │ ├── polling.html │ │ │ └── polling.js │ │ ├── repeater │ │ │ ├── repeater.html │ │ │ └── repeater.js │ │ ├── scripts │ │ │ └── web-server.js │ │ └── shadow │ │ │ ├── shadow.html │ │ │ └── shadow.js │ ├── testdriver.py │ └── testserver.py └── unit │ ├── __init__.py │ ├── test_mixins.py │ └── test_webdriver.py └── webdriver.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | # build directories 4 | build/ 5 | dist/ 6 | # Eclipse - Pydev 7 | .project 8 | .pydevproject 9 | .settings 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | # command to install dependencies 6 | install: 7 | - pip install . 8 | - pip install -r requirements.txt 9 | # command to run tests 10 | script: nosetests pytractor.tests.unit 11 | -------------------------------------------------------------------------------- /DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | pytractor provides utilities for testing Angular.js applications with selenium for Python. Selenium webdrivers are extended with functionality for dealing with Angular.js applications. 2 | 3 | pytractor uses scripts provided by protractor (the javascript testing framework for Angular.js). 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include src/pytractor/protractor/extracted *.js 2 | recursive-include src/pytractor/tests/functional/testapp * 3 | include DESCRIPTION.rst 4 | include NOTICE 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Software Credits 2 | 3 | This software contains parts of protractor: 4 | Protractor by Google Inc. https://github.com/angular/protractor 5 | Licensed Under: MIT License 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | pytractor 3 | ========= 4 | .. image:: https://travis-ci.org/kpodl/pytractor.svg?branch=master 5 | :target: https://travis-ci.org/kpodl/pytractor 6 | :alt: Build Status 7 | 8 | **Angular.js for the testing goat: Utilities for testing Angular.js applications with Selenium for Python.** 9 | 10 | ================================ 11 | This project has been abandoned! 12 | ================================ 13 | **Unfortunately, due to lack of time and a switch to robotframework-extendedselenium2library this project has been abandoned. Please contact me if you want to take over.** 14 | 15 | Overview 16 | -------- 17 | 18 | *pytractor* is an extension to the `Selenium bindings for Python `_. Its goal is to make testing of angular.js applications easier with Python. 19 | 20 | It is built on some parts of `protractor `_, the "official" Javascript E2E/Scenario testing framework for `Angular.js `_. 21 | 22 | 23 | Usage 24 | ----- 25 | 26 | It is assumed that you are familiar with with the `Selenium bindings for Python `_. 27 | 28 | Basics 29 | ====== 30 | 31 | Drivers containing the helper methods for testing Angular.js can be found in the ``pytractor.webdriver`` module. 32 | 33 | The constructor expects the base URL of your application. 34 | 35 | :: 36 | 37 | from pytractor.webdriver import Firefox 38 | 39 | driver = Firefox('http://localhost:8080/base_url') 40 | 41 | The base URL will be prepended to each URL you pass to the ``get()`` method (using ``urlparse.urljoin(base_url, get_url)``). 42 | 43 | The constructor also accepts the parameters: 44 | 45 | ``root_element`` 46 | A selector for the DOM element that is the root of your Angular.js app (default: ``'body'``). 47 | ``script_timeout`` 48 | The amount of seconds (default: 10) to wait for a script executing asynchroneously (see selenium's ``set_script_timeout()``. 49 | ``test_timeout`` 50 | The amount of seconds (default: 10) to wait for the script which verifies whether Angular.js is indeed used on the page. 51 | 52 | If no Angular.js app can be found, ``get()`` will raise an exception. 53 | 54 | The usual selenium webdriver methods can be used, but pytractor will wait for Angular.js to finish processing for some of them. 55 | 56 | Additional methods 57 | ================== 58 | 59 | Finding Elements 60 | ^^^^^^^^^^^^^^^^ 61 | 62 | Finding elements by binding 63 | +++++++++++++++++++++++++++ 64 | The ``find_element(s)_by_binding()`` methods retrieve the element(s) which use the specified binding. 65 | 66 | Suppose your Angular app contains a binding of the form 67 | 68 | :: 69 | 70 |
{{my_binding}}
71 | 72 | Then you can locate the ``
`` with 73 | 74 | :: 75 | 76 | driver.find_element_by_binding('my_binding') 77 | 78 | ``find_element(s)_by_binding()`` will also find elements if only part of the binding 79 | name is specified. 80 | In other words, the binding 81 | 82 | :: 83 | 84 |
{{my_binding}}
85 | 86 | can be found with 87 | 88 | :: 89 | 90 | driver.find_element_by_binding('my_bind') 91 | 92 | 93 | If you want to locate the binding by its **exact** name, use 94 | ``find_element(s)_by_exact_binding()``. 95 | 96 | Finding elements by model 97 | +++++++++++++++++++++++++ 98 | ``find_element(s)_by_model('model_name')`` can be used to locate elements that 99 | use the expression ``ng-model="model_name"``. 100 | 101 | Suppose you have the element 102 | :: 103 | 104 | 105 | 106 | then the ```` element can be found with 107 | :: 108 | 109 | driver.find_element_by_model('person.name') 110 | 111 | Finding elements by repeater 112 | ++++++++++++++++++++++++++++ 113 | ``find_element(s)_by_repeater('item in list')`` can be used to locate elements that 114 | use the expression ``ng-repeat="item in list"``. 115 | 116 | 117 | Other Methods and Properties 118 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 119 | 120 | Refreshing a page 121 | +++++++++++++++++ 122 | 123 | ``refresh()`` will perform a full reload of the current page. It assumes that 124 | the page uses Angular.js. 125 | 126 | Using in-page navigation 127 | ++++++++++++++++++++++++ 128 | 129 | ``set_location(url)`` will use the in-page navigation (just as ``$location.url()``). 130 | 131 | Retrieving the absolute URL 132 | +++++++++++++++++++++++++++ 133 | The ``location_abs_url`` property will retrieve the absolute URL from angular. 134 | 135 | 136 | Missing Features 137 | ---------------- 138 | 139 | - Button text, and options locators. 140 | - Script/mock module injection. 141 | 142 | License 143 | ------- 144 | 145 | pytractor is licensed under the the Apache License, Version 2.0: 146 | http://www.apache.org/licenses/LICENSE-2.0 147 | 148 | protractor is Copyright (c) 2010-2014 Google, Inc. and licensed under the MIT license. See the respective directory for a copy of the license. 149 | 150 | Credits 151 | ------- 152 | Credits for the client-side scripts go to the `protractor `_ project for their fine framework. 153 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=no 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | # Use multiple processes to speed up Pylint. 25 | jobs=1 26 | 27 | # Allow loading of arbitrary C extensions. Extensions are imported into the 28 | # active Python interpreter and may run arbitrary code. 29 | unsafe-load-any-extension=no 30 | 31 | # A comma-separated list of package or module names from where C extensions may 32 | # be loaded. Extensions are loading into the active Python interpreter and may 33 | # run arbitrary code 34 | extension-pkg-whitelist= 35 | 36 | # Allow optimization of some AST trees. This will activate a peephole AST 37 | # optimizer, which will apply various small optimizations. For instance, it can 38 | # be used to obtain the result of joining multiple strings with the addition 39 | # operator. Joining a lot of strings can lead to a maximum recursion error in 40 | # Pylint and this flag can prevent that. It has one side effect, the resulting 41 | # AST will be different than the one from reality. 42 | optimize-ast=no 43 | 44 | 45 | [MESSAGES CONTROL] 46 | 47 | # Only show warnings with the listed confidence levels. Leave empty to show 48 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 49 | confidence= 50 | 51 | # Enable the message, report, category or checker with the given id(s). You can 52 | # either give multiple identifier separated by comma (,) or put this option 53 | # multiple time. See also the "--disable" option for examples. 54 | #enable= 55 | 56 | # Disable the message, report, category or checker with the given id(s). You 57 | # can either give multiple identifiers separated by comma (,) or put this 58 | # option multiple times (only on the command line, not in the configuration 59 | # file where it should appear only once).You can also use "--disable=all" to 60 | # disable everything first and then reenable specific checks. For example, if 61 | # you want to run only the similarities checker, you can use "--disable=all 62 | # --enable=similarities". If you want to run only the classes checker, but have 63 | # no Warning level messages displayed, use"--disable=all --enable=classes 64 | # --disable=W" 65 | disable=E1608,W1627,E1601,E1603,E1602,E1605,E1604,E1607,E1606,W1621,W1620,W1623,W1622,W1625,W1624,W1609,W1608,W1607,W1606,W1605,W1604,W1603,W1602,W1601,W1639,W1640,I0021,W1638,I0020,W1618,W1619,W1630,W1626,W1637,W1634,W1635,W1610,W1611,W1612,W1613,W1614,W1615,W1616,W1617,W1632,W1633,W0704,W1628,W1629,W1636,missing-docstring 66 | 67 | 68 | [REPORTS] 69 | 70 | # Set the output format. Available formats are text, parseable, colorized, msvs 71 | # (visual studio) and html. You can also give a reporter class, eg 72 | # mypackage.mymodule.MyReporterClass. 73 | output-format=text 74 | 75 | # Put messages in a separate file for each module / package specified on the 76 | # command line instead of printing them on stdout. Reports (if any) will be 77 | # written in a file name "pylint_global.[txt|html]". 78 | files-output=no 79 | 80 | # Tells whether to display a full report or only the messages 81 | reports=yes 82 | 83 | # Python expression which should return a note less than 10 (10 is the highest 84 | # note). You have access to the variables errors warning, statement which 85 | # respectively contain the number of errors / warnings messages and the total 86 | # number of statements analyzed. This is used by the global evaluation report 87 | # (RP0004). 88 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 89 | 90 | # Add a comment according to your evaluation note. This is used by the global 91 | # evaluation report (RP0004). 92 | comment=no 93 | 94 | # Template used to display messages. This is a python new-style format string 95 | # used to format the message information. See doc for all details 96 | #msg-template= 97 | 98 | 99 | [BASIC] 100 | 101 | # Required attributes for module, separated by a comma 102 | required-attributes= 103 | 104 | # List of builtins function names that should not be used, separated by a comma 105 | bad-functions=map,filter,input 106 | 107 | # Good variable names which should always be accepted, separated by a comma 108 | good-names=i,j,k,ex,Run,_ 109 | 110 | # Bad variable names which should always be refused, separated by a comma 111 | bad-names=foo,bar,baz,toto,tutu,tata 112 | 113 | # Colon-delimited sets of names that determine each other's naming style when 114 | # the name regexes allow several styles. 115 | name-group= 116 | 117 | # Include a hint for the correct naming format with invalid-name 118 | include-naming-hint=no 119 | 120 | # Regular expression matching correct function names 121 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 122 | 123 | # Naming hint for function names 124 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 125 | 126 | # Regular expression matching correct variable names 127 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 128 | 129 | # Naming hint for variable names 130 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 131 | 132 | # Regular expression matching correct constant names 133 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 134 | 135 | # Naming hint for constant names 136 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 137 | 138 | # Regular expression matching correct attribute names 139 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 140 | 141 | # Naming hint for attribute names 142 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 143 | 144 | # Regular expression matching correct argument names 145 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 146 | 147 | # Naming hint for argument names 148 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 149 | 150 | # Regular expression matching correct class attribute names 151 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 152 | 153 | # Naming hint for class attribute names 154 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 155 | 156 | # Regular expression matching correct inline iteration names 157 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 158 | 159 | # Naming hint for inline iteration names 160 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 161 | 162 | # Regular expression matching correct class names 163 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 164 | 165 | # Naming hint for class names 166 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 167 | 168 | # Regular expression matching correct module names 169 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 170 | 171 | # Naming hint for module names 172 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 173 | 174 | # Regular expression matching correct method names 175 | method-rgx=[a-z_][a-z0-9_]{2,70}$ 176 | 177 | # Naming hint for method names 178 | method-name-hint=[a-z_][a-z0-9_]{2,70}$ 179 | 180 | # Regular expression which should only match function or class names that do 181 | # not require a docstring. 182 | no-docstring-rgx=__.*__ 183 | 184 | # Minimum line length for functions/classes that require docstrings, shorter 185 | # ones are exempt. 186 | docstring-min-length=-1 187 | 188 | 189 | [FORMAT] 190 | 191 | # Maximum number of characters on a single line. 192 | max-line-length=100 193 | 194 | # Regexp for a line that is allowed to be longer than the limit. 195 | ignore-long-lines=^\s*(# )??$ 196 | 197 | # Allow the body of an if to be on the same line as the test if there is no 198 | # else. 199 | single-line-if-stmt=no 200 | 201 | # List of optional constructs for which whitespace checking is disabled 202 | no-space-check=trailing-comma,dict-separator 203 | 204 | # Maximum number of lines in a module 205 | max-module-lines=1000 206 | 207 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 208 | # tab). 209 | indent-string=' ' 210 | 211 | # Number of spaces of indent required inside a hanging or continued line. 212 | indent-after-paren=4 213 | 214 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 215 | expected-line-ending-format= 216 | 217 | 218 | [VARIABLES] 219 | 220 | # Tells whether we should check for unused import in __init__ files. 221 | init-import=no 222 | 223 | # A regular expression matching the name of dummy variables (i.e. expectedly 224 | # not used). 225 | dummy-variables-rgx=_$|dummy 226 | 227 | # List of additional names supposed to be defined in builtins. Remember that 228 | # you should avoid to define new builtins when possible. 229 | additional-builtins= 230 | 231 | # List of strings which can identify a callback function by name. A callback 232 | # name must start or end with one of those strings. 233 | callbacks=cb_,_cb 234 | 235 | 236 | [LOGGING] 237 | 238 | # Logging modules to check that the string format arguments are in logging 239 | # function parameter format 240 | logging-modules=logging 241 | 242 | 243 | [SPELLING] 244 | 245 | # Spelling dictionary name. Available dictionaries: none. To make it working 246 | # install python-enchant package. 247 | spelling-dict= 248 | 249 | # List of comma separated words that should not be checked. 250 | spelling-ignore-words= 251 | 252 | # A path to a file that contains private dictionary; one word per line. 253 | spelling-private-dict-file= 254 | 255 | # Tells whether to store unknown words to indicated private dictionary in 256 | # --spelling-private-dict-file option instead of raising a message. 257 | spelling-store-unknown-words=no 258 | 259 | 260 | [SIMILARITIES] 261 | 262 | # Minimum lines number of a similarity. 263 | min-similarity-lines=4 264 | 265 | # Ignore comments when computing similarities. 266 | ignore-comments=yes 267 | 268 | # Ignore docstrings when computing similarities. 269 | ignore-docstrings=yes 270 | 271 | # Ignore imports when computing similarities. 272 | ignore-imports=no 273 | 274 | 275 | [TYPECHECK] 276 | 277 | # Tells whether missing members accessed in mixin class should be ignored. A 278 | # mixin class is detected if its name ends with "mixin" (case insensitive). 279 | ignore-mixin-members=yes 280 | 281 | # List of module names for which member attributes should not be checked 282 | # (useful for modules/projects where namespaces are manipulated during runtime 283 | # and thus existing member attributes cannot be deduced by static analysis 284 | ignored-modules= 285 | 286 | # List of classes names for which member attributes should not be checked 287 | # (useful for classes with attributes dynamically set). 288 | ignored-classes=SQLObject 289 | 290 | # When zope mode is activated, add a predefined set of Zope acquired attributes 291 | # to generated-members. 292 | zope=no 293 | 294 | # List of members which are set dynamically and missed by pylint inference 295 | # system, and so shouldn't trigger E0201 when accessed. Python regular 296 | # expressions are accepted. 297 | generated-members=REQUEST,acl_users,aq_parent 298 | 299 | 300 | [MISCELLANEOUS] 301 | 302 | # List of note tags to take in consideration, separated by a comma. 303 | notes=FIXME,XXX,TODO 304 | 305 | 306 | [DESIGN] 307 | 308 | # Maximum number of arguments for function / method 309 | max-args=5 310 | 311 | # Argument names that match this expression will be ignored. Default to name 312 | # with leading underscore 313 | ignored-argument-names=_.* 314 | 315 | # Maximum number of locals for function / method body 316 | max-locals=15 317 | 318 | # Maximum number of return / yield for function / method body 319 | max-returns=6 320 | 321 | # Maximum number of branch for function / method body 322 | max-branches=12 323 | 324 | # Maximum number of statements in function / method body 325 | max-statements=50 326 | 327 | # Maximum number of parents for a class (see R0901). 328 | max-parents=7 329 | 330 | # Maximum number of attributes for a class (see R0902). 331 | max-attributes=7 332 | 333 | # Minimum number of public methods for a class (see R0903). 334 | min-public-methods=2 335 | 336 | # Maximum number of public methods for a class (see R0904). 337 | max-public-methods=20 338 | 339 | 340 | [CLASSES] 341 | 342 | # List of interface methods to ignore, separated by a comma. This is used for 343 | # instance to not check methods defines in Zope's Interface base class. 344 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 345 | 346 | # List of method names used to declare (i.e. assign) instance attributes. 347 | defining-attr-methods=__init__,__new__,setUp 348 | 349 | # List of valid names for the first argument in a class method. 350 | valid-classmethod-first-arg=cls 351 | 352 | # List of valid names for the first argument in a metaclass class method. 353 | valid-metaclass-classmethod-first-arg=mcs 354 | 355 | # List of member names, which should be excluded from the protected access 356 | # warning. 357 | exclude-protected=_asdict,_fields,_replace,_source,_make 358 | 359 | 360 | [IMPORTS] 361 | 362 | # Deprecated modules which should not be used, separated by a comma 363 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 364 | 365 | # Create a graph of every (i.e. internal and external) dependencies in the 366 | # given file (report RP0402 must not be disabled) 367 | import-graph= 368 | 369 | # Create a graph of external dependencies in the given file (report RP0402 must 370 | # not be disabled) 371 | ext-import-graph= 372 | 373 | # Create a graph of internal dependencies in the given file (report RP0402 must 374 | # not be disabled) 375 | int-import-graph= 376 | 377 | 378 | [EXCEPTIONS] 379 | 380 | # Exceptions that will emit a warning when being caught. Defaults to 381 | # "Exception" 382 | overgeneral-exceptions=Exception 383 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | funcsigs==0.4 2 | future==0.15.2 3 | mock==1.3.0 4 | nose==1.3.7 5 | pbr==1.8.1 6 | selenium==2.48.0 7 | six==1.10.0 8 | -------------------------------------------------------------------------------- /scripts/build-and-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ ! -f ./setup.py ]; then 3 | echo "This script is meant to be run in the top-level source directory." 4 | exit 1 5 | fi 6 | 7 | if [ -d dist ]; then 8 | echo "Please remove the dist/ directory before running the script." 9 | exit 1 10 | fi 11 | 12 | echo "===" 13 | echo "BUILDING" 14 | echo "===" 15 | ./setup.py sdist bdist_wheel 16 | 17 | if [ $? -eq 0 ]; then 18 | echo "===" 19 | echo "UPLOADING" 20 | echo "===" 21 | twine upload dist/*.tar.gz dist/*.whl 22 | else 23 | echo "Build did not complete successfully. Not uploading." 24 | exit 1 25 | fi 26 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2014 Konrad Podloucky 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | """ 19 | Setuptools script for building pytractor. 20 | """ 21 | 22 | from setuptools import setup, find_packages 23 | from codecs import open # To use a consistent encoding 24 | from os import path 25 | 26 | here = path.abspath(path.dirname(__file__)) 27 | 28 | # Get the long description from the relevant file 29 | with open(path.join(here, 'DESCRIPTION.rst'), encoding='utf-8') as f: 30 | long_description = f.read() 31 | 32 | setup( 33 | name='pytractor', 34 | 35 | version='0.2.2.dev1', 36 | 37 | description='Selenium testing for Angular.js apps', 38 | long_description=long_description, 39 | 40 | url='https://github.com/kpodl/pytractor', 41 | 42 | author='Konrad Podloucky', 43 | author_email='konrad+pytractor@crunchy-frog.org', 44 | 45 | license='Apache 2.0', 46 | 47 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 48 | classifiers=[ 49 | 'Development Status :: 3 - Alpha', 50 | 51 | 'Intended Audience :: Developers', 52 | 'Topic :: Software Development :: Testing', 53 | 54 | 'License :: OSI Approved :: Apache Software License', 55 | 56 | 'Programming Language :: Python :: 2.7', 57 | ], 58 | 59 | keywords='selenium angular.js testing', 60 | 61 | package_dir={'': 'src'}, 62 | 63 | packages=find_packages('src'), 64 | 65 | # For package data see MANIFEST.in 66 | include_package_data=True, 67 | 68 | install_requires=[ 69 | 'selenium>=2.48.0', 70 | 'future>=0.15.2' 71 | ], 72 | 73 | tests_require=[ 74 | 'nose>=1.3.7', 75 | 'mock>=1.3.0', 76 | ], 77 | test_suite='nose.collector', 78 | use_2to3=True 79 | ) 80 | -------------------------------------------------------------------------------- /src/pytractor/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Pytractor module 16 | """ 17 | -------------------------------------------------------------------------------- /src/pytractor/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from selenium.common.exceptions import WebDriverException 16 | 17 | 18 | class AngularNotFoundException(WebDriverException): 19 | """An exception raised if angular.js could not be found on a web page.""" 20 | -------------------------------------------------------------------------------- /src/pytractor/mixins.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky, Michal Walkowski 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Mixin to add the capability of testing angular.js apps with selenium 16 | webdrivers. 17 | """ 18 | 19 | from functools import wraps 20 | from math import floor 21 | from future.moves.urllib.parse import urljoin 22 | 23 | from selenium.common.exceptions import NoSuchElementException 24 | from selenium.webdriver.remote.command import Command 25 | from selenium.webdriver.support.wait import WebDriverWait 26 | 27 | from pkg_resources import resource_string 28 | 29 | from .exceptions import AngularNotFoundException 30 | 31 | CLIENT_SCRIPTS_DIR = 'protractor/extracted' 32 | DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!' 33 | # These are commands that need synchronization with the angular app. 34 | COMMANDS_NEEDING_WAIT = [ 35 | Command.CLICK_ELEMENT, 36 | Command.SEND_KEYS_TO_ELEMENT, 37 | Command.GET_ELEMENT_TAG_NAME, 38 | Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, 39 | Command.GET_ELEMENT_ATTRIBUTE, 40 | Command.GET_ELEMENT_TEXT, 41 | Command.GET_ELEMENT_SIZE, 42 | Command.GET_ELEMENT_LOCATION, 43 | Command.IS_ELEMENT_ENABLED, 44 | Command.IS_ELEMENT_SELECTED, 45 | Command.IS_ELEMENT_DISPLAYED, 46 | Command.SUBMIT_ELEMENT, 47 | Command.CLEAR_ELEMENT 48 | ] 49 | 50 | 51 | def angular_wait_required(wrapped): 52 | @wraps(wrapped) 53 | def wait_for_angular(driver, *args, **kwargs): 54 | driver.wait_for_angular() 55 | return wrapped(driver, *args, **kwargs) 56 | return wait_for_angular 57 | 58 | 59 | class WebDriverMixin(object): 60 | """A mixin for Selenium Webdrivers.""" 61 | ignore_synchronization = False 62 | """If True, pytractor will not attempt to synchronize with the page before 63 | performing actions. This can be harmful because pytractor will not wait 64 | until $timeouts and $http calls have been processed, which can cause 65 | tests to become flaky. This should be used only when necessary, such as 66 | when a page continuously polls an API using $timeout. 67 | """ # docstring adapted from protractor.js 68 | 69 | def __init__(self, base_url='', root_element='body', script_timeout=10, 70 | test_timeout=10, *args, **kwargs): 71 | self._base_url = base_url 72 | self._root_element = root_element 73 | self._test_timeout = test_timeout 74 | super(WebDriverMixin, self).__init__(*args, **kwargs) 75 | self.set_script_timeout(script_timeout) 76 | 77 | def _execute_client_script(self, script_name, *args, **kwargs): 78 | async = kwargs.pop('async', True) 79 | file_name = '{}.js'.format(script_name) 80 | js_script = resource_string(__name__, 81 | '{}/{}'.format(CLIENT_SCRIPTS_DIR, 82 | file_name)) 83 | if js_script: 84 | js_script = js_script.decode('UTF-8') 85 | if async: 86 | result = self.execute_async_script(js_script, *args) 87 | else: 88 | result = self.execute_script(js_script, *args) 89 | return result 90 | 91 | def wait_for_angular(self): 92 | if self.ignore_synchronization: 93 | return 94 | else: 95 | return self._execute_client_script('waitForAngular', 96 | self._root_element, 97 | async=True) 98 | 99 | def execute(self, driver_command, params=None): 100 | # We also get called from WebElement methods/properties. 101 | if driver_command in COMMANDS_NEEDING_WAIT: 102 | self.wait_for_angular() 103 | 104 | return super(WebDriverMixin, self).execute(driver_command, 105 | params=params) 106 | 107 | def _test_for_angular(self): 108 | return self._execute_client_script('testForAngular', 109 | floor(self._test_timeout / 1000)) 110 | 111 | def _location_equals(self, location): 112 | result = self.execute_script('return window.location.href') 113 | return result == location 114 | 115 | @property 116 | @angular_wait_required 117 | def current_url(self): 118 | return super(WebDriverMixin, self).current_url 119 | 120 | @property 121 | @angular_wait_required 122 | def page_source(self): 123 | return super(WebDriverMixin, self).page_source 124 | 125 | @property 126 | @angular_wait_required 127 | def title(self): 128 | return super(WebDriverMixin, self).title 129 | 130 | @property 131 | @angular_wait_required 132 | def location_abs_url(self): 133 | return self._execute_client_script('getLocationAbsUrl', 134 | self._root_element, async=False) 135 | 136 | @angular_wait_required 137 | def find_elements_by_repeater(self, descriptor, using=None): 138 | return self._execute_client_script('findAllRepeaterRows', 139 | descriptor, False, using, 140 | async=False) 141 | 142 | @angular_wait_required 143 | def find_element(self, *args, **kwargs): 144 | return super(WebDriverMixin, self).find_element(*args, **kwargs) 145 | 146 | @angular_wait_required 147 | def find_elements(self, *args, **kwargs): 148 | return super(WebDriverMixin, self).find_elements(*args, **kwargs) 149 | 150 | @angular_wait_required 151 | def find_elements_by_binding(self, descriptor, using=None): 152 | elements = self._execute_client_script('findBindings', descriptor, 153 | False, using, async=False) 154 | return elements 155 | 156 | def find_element_by_binding(self, descriptor, using=None): 157 | elements = self.find_elements_by_binding(descriptor, using=using) 158 | if len(elements) == 0: 159 | raise NoSuchElementException( 160 | "No element found for binding descriptor" 161 | " '{}'".format(descriptor) 162 | ) 163 | else: 164 | return elements[0] 165 | 166 | def find_element_by_exact_binding(self, descriptor, using=None): 167 | elements = self.find_elements_by_exact_binding(descriptor, using=using) 168 | if len(elements) == 0: 169 | raise NoSuchElementException( 170 | "No element found for binding descriptor" 171 | " '{}'".format(descriptor) 172 | ) 173 | else: 174 | return elements[0] 175 | 176 | @angular_wait_required 177 | def find_elements_by_exact_binding(self, descriptor, using=None): 178 | elements = self._execute_client_script('findBindings', descriptor, 179 | True, using, async=False) 180 | return elements 181 | 182 | def find_element_by_model(self, descriptor, using=None): 183 | elements = self.find_elements_by_model(descriptor, using=using) 184 | if len(elements) == 0: 185 | raise NoSuchElementException( 186 | "No element found for model descriptor" 187 | " {}".format(descriptor) 188 | ) 189 | else: 190 | return elements[0] 191 | 192 | @angular_wait_required 193 | def find_elements_by_model(self, descriptor, using=None): 194 | elements = self._execute_client_script('findByModel', descriptor, 195 | using, async=False) 196 | # Workaround for issue #10: findByModel.js returns None instead of empty 197 | # list if no element has been found. 198 | if elements is None: 199 | elements = [] 200 | return elements 201 | 202 | def get(self, url): 203 | super(WebDriverMixin, self).get('about:blank') 204 | full_url = urljoin(str(self._base_url), str(url)) 205 | self.execute_script( 206 | """ 207 | window.name = "{}" + window.name; 208 | window.location.replace("{}"); 209 | """.format(DEFER_LABEL, full_url) 210 | ) 211 | wait = WebDriverWait(self, self._test_timeout) 212 | wait.until_not(self._location_equals, 'about:blank') 213 | 214 | if not self.ignore_synchronization: 215 | test_result = self._test_for_angular() 216 | angular_on_page = test_result[0] 217 | if not angular_on_page: 218 | message = test_result[1] 219 | raise AngularNotFoundException( 220 | 'Angular could not be found on page: {}:' 221 | ' {}'.format(full_url, message) 222 | ) 223 | # TODO: inject scripts here 224 | # return self.execute_script( 225 | # 'angular.resumeBootstrap(arguments[0]);' 226 | # ) 227 | self.execute_script('angular.resumeBootstrap();') 228 | 229 | def refresh(self): 230 | url = self.execute_script('return window.location.href') 231 | self.get(url) 232 | 233 | @angular_wait_required 234 | def set_location(self, url): 235 | result = self._execute_client_script('setLocation', self._root_element, 236 | url, async=False) 237 | return result 238 | -------------------------------------------------------------------------------- /src/pytractor/protractor/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /src/pytractor/protractor/extract-clientsidescripts.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | EXTRACTED_DIR = 'extracted' 4 | 5 | fs = require 'fs' 6 | client_side_scripts = require './node_modules/protractor/lib/clientsidescripts.js' 7 | 8 | for name, script of client_side_scripts 9 | console.log "Extracting '#{name}'" 10 | fs.writeFileSync "#{EXTRACTED_DIR}/#{name}.js", script 11 | console.log " #{name} extracted successfully." 12 | -------------------------------------------------------------------------------- /src/pytractor/protractor/extract-clientsidescripts.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | (function() { 3 | 'use strict'; 4 | 5 | var EXTRACTED_DIR, client_side_scripts, fs, name, script; 6 | 7 | EXTRACTED_DIR = 'extracted'; 8 | 9 | fs = require('fs'); 10 | 11 | client_side_scripts = require('./node_modules/protractor/lib/clientsidescripts.js'); 12 | 13 | for (name in client_side_scripts) { 14 | script = client_side_scripts[name]; 15 | console.log("Extracting '" + name + "'"); 16 | fs.writeFileSync("" + EXTRACTED_DIR + "/" + name + ".js", script); 17 | console.log(" " + name + " extracted successfully."); 18 | } 19 | 20 | }).call(this); 21 | -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2014 Google, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/allowAnimations.js: -------------------------------------------------------------------------------- 1 | try { return (function (element, value) { 2 | var ngElement = angular.element(element); 3 | if (ngElement.allowAnimations) { 4 | // AngularDart: $testability API. 5 | return ngElement.allowAnimations(value); 6 | } else { 7 | // AngularJS 8 | var enabledFn = ngElement.injector().get('$animate').enabled; 9 | return (value == null) ? enabledFn() : enabledFn(value); 10 | } 11 | }).apply(this, arguments); } 12 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/evaluate.js: -------------------------------------------------------------------------------- 1 | try { return (function (element, expression) { 2 | return angular.element(element).scope().$eval(expression); 3 | }).apply(this, arguments); } 4 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findAllRepeaterRows.js: -------------------------------------------------------------------------------- 1 | try { return (function anonymous() { 2 | function repeaterMatch(ngRepeat, repeater, exact) { 3 | if (exact) { 4 | return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0]. 5 | split('=')[0].trim() == repeater; 6 | } else { 7 | return ngRepeat.indexOf(repeater) != -1; 8 | } 9 | }; return (function findAllRepeaterRows(repeater, exact, using) { 10 | using = using || document; 11 | 12 | var rows = []; 13 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 14 | for (var p = 0; p < prefixes.length; ++p) { 15 | var attr = prefixes[p] + 'repeat'; 16 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 17 | attr = attr.replace(/\\/g, ''); 18 | for (var i = 0; i < repeatElems.length; ++i) { 19 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 20 | rows.push(repeatElems[i]); 21 | } 22 | } 23 | } 24 | for (var p = 0; p < prefixes.length; ++p) { 25 | var attr = prefixes[p] + 'repeat-start'; 26 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 27 | attr = attr.replace(/\\/g, ''); 28 | for (var i = 0; i < repeatElems.length; ++i) { 29 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 30 | var elem = repeatElems[i]; 31 | while (elem.nodeType != 8 || 32 | !repeaterMatch(elem.nodeValue, repeater, exact)) { 33 | if (elem.nodeType == 1) { 34 | rows.push(elem); 35 | } 36 | elem = elem.nextSibling; 37 | } 38 | } 39 | } 40 | } 41 | return rows; 42 | }).apply(this, arguments); 43 | }).apply(this, arguments); } 44 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findBindings.js: -------------------------------------------------------------------------------- 1 | try { return (function (binding, exactMatch, using, rootSelector) { 2 | var root = document.querySelector(rootSelector || 'body'); 3 | using = using || document; 4 | if (angular.getTestability) { 5 | return angular.getTestability(root). 6 | findBindings(using, binding, exactMatch); 7 | } 8 | var bindings = using.getElementsByClassName('ng-binding'); 9 | var matches = []; 10 | for (var i = 0; i < bindings.length; ++i) { 11 | var dataBinding = angular.element(bindings[i]).data('$binding'); 12 | if (dataBinding) { 13 | var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding; 14 | if (exactMatch) { 15 | var matcher = new RegExp('({|\\s|^|\\|)' + 16 | /* See http://stackoverflow.com/q/3561711 */ 17 | binding.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&') + 18 | '(}|\\s|$|\\|)'); 19 | if (matcher.test(bindingName)) { 20 | matches.push(bindings[i]); 21 | } 22 | } else { 23 | if (bindingName.indexOf(binding) != -1) { 24 | matches.push(bindings[i]); 25 | } 26 | } 27 | 28 | } 29 | } 30 | return matches; /* Return the whole array for webdriver.findElements. */ 31 | }).apply(this, arguments); } 32 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findByButtonText.js: -------------------------------------------------------------------------------- 1 | try { return (function (searchText, using) { 2 | using = using || document; 3 | 4 | var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]'); 5 | var matches = []; 6 | for (var i = 0; i < elements.length; ++i) { 7 | var element = elements[i]; 8 | var elementText; 9 | if (element.tagName.toLowerCase() == 'button') { 10 | elementText = element.textContent || element.innerText || ''; 11 | } else { 12 | elementText = element.value; 13 | } 14 | if (elementText.trim() === searchText) { 15 | matches.push(element); 16 | } 17 | } 18 | 19 | return matches; 20 | }).apply(this, arguments); } 21 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findByCssContainingText.js: -------------------------------------------------------------------------------- 1 | try { return (function (cssSelector, searchText, using) { 2 | using = using || document; 3 | 4 | var elements = using.querySelectorAll(cssSelector); 5 | var matches = []; 6 | for (var i = 0; i < elements.length; ++i) { 7 | var element = elements[i]; 8 | var elementText = element.textContent || element.innerText || ''; 9 | if (elementText.indexOf(searchText) > -1) { 10 | matches.push(element); 11 | } 12 | } 13 | return matches; 14 | }).apply(this, arguments); } 15 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findByModel.js: -------------------------------------------------------------------------------- 1 | try { return (function (model, using, rootSelector) { 2 | var root = document.querySelector(rootSelector || 'body'); 3 | using = using || document; 4 | 5 | if (angular.getTestability) { 6 | return angular.getTestability(root). 7 | findModels(using, model, true); 8 | } 9 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 10 | for (var p = 0; p < prefixes.length; ++p) { 11 | var selector = '[' + prefixes[p] + 'model="' + model + '"]'; 12 | var elements = using.querySelectorAll(selector); 13 | if (elements.length) { 14 | return elements; 15 | } 16 | } 17 | }).apply(this, arguments); } 18 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findByOptions.js: -------------------------------------------------------------------------------- 1 | try { return (function (optionsDescriptor, using) { 2 | using = using || document; 3 | 4 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 5 | for (var p = 0; p < prefixes.length; ++p) { 6 | var selector = '[' + prefixes[p] + 'options="' + optionsDescriptor + '"] option'; 7 | var elements = using.querySelectorAll(selector); 8 | if (elements.length) { 9 | return elements; 10 | } 11 | } 12 | }).apply(this, arguments); } 13 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findByPartialButtonText.js: -------------------------------------------------------------------------------- 1 | try { return (function (searchText, using) { 2 | using = using || document; 3 | 4 | var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]'); 5 | var matches = []; 6 | for (var i = 0; i < elements.length; ++i) { 7 | var element = elements[i]; 8 | var elementText; 9 | if (element.tagName.toLowerCase() == 'button') { 10 | elementText = element.textContent || element.innerText || ''; 11 | } else { 12 | elementText = element.value; 13 | } 14 | if (elementText.indexOf(searchText) > -1) { 15 | matches.push(element); 16 | } 17 | } 18 | 19 | return matches; 20 | }).apply(this, arguments); } 21 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findRepeaterColumn.js: -------------------------------------------------------------------------------- 1 | try { return (function anonymous() { 2 | function repeaterMatch(ngRepeat, repeater, exact) { 3 | if (exact) { 4 | return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0]. 5 | split('=')[0].trim() == repeater; 6 | } else { 7 | return ngRepeat.indexOf(repeater) != -1; 8 | } 9 | }; return (function findRepeaterColumn(repeater, exact, binding, using, rootSelector) { 10 | var matches = []; 11 | var root = document.querySelector(rootSelector || 'body'); 12 | using = using || document; 13 | 14 | var rows = []; 15 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 16 | for (var p = 0; p < prefixes.length; ++p) { 17 | var attr = prefixes[p] + 'repeat'; 18 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 19 | attr = attr.replace(/\\/g, ''); 20 | for (var i = 0; i < repeatElems.length; ++i) { 21 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 22 | rows.push(repeatElems[i]); 23 | } 24 | } 25 | } 26 | /* multiRows is an array of arrays, where each inner array contains 27 | one row of elements. */ 28 | var multiRows = []; 29 | for (var p = 0; p < prefixes.length; ++p) { 30 | var attr = prefixes[p] + 'repeat-start'; 31 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 32 | attr = attr.replace(/\\/g, ''); 33 | for (var i = 0; i < repeatElems.length; ++i) { 34 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 35 | var elem = repeatElems[i]; 36 | var row = []; 37 | while (elem.nodeType != 8 || (elem.nodeValue && 38 | !repeaterMatch(elem.nodeValue, repeater, exact))) { 39 | if (elem.nodeType == 1) { 40 | row.push(elem); 41 | } 42 | elem = elem.nextSibling; 43 | } 44 | multiRows.push(row); 45 | } 46 | } 47 | } 48 | var bindings = []; 49 | for (var i = 0; i < rows.length; ++i) { 50 | if (angular.getTestability) { 51 | matches.push.apply( 52 | matches, 53 | angular.getTestability(root).findBindings(rows[i], binding)); 54 | } else { 55 | if (rows[i].className.indexOf('ng-binding') != -1) { 56 | bindings.push(rows[i]); 57 | } 58 | var childBindings = rows[i].getElementsByClassName('ng-binding'); 59 | for (var k = 0; k < childBindings.length; ++k) { 60 | bindings.push(childBindings[k]); 61 | } 62 | } 63 | } 64 | for (var i = 0; i < multiRows.length; ++i) { 65 | for (var j = 0; j < multiRows[i].length; ++j) { 66 | if (angular.getTestability) { 67 | matches.push.apply( 68 | matches, 69 | angular.getTestability(root).findBindings(multiRows[i][j], binding)); 70 | } else { 71 | var elem = multiRows[i][j]; 72 | if (elem.className.indexOf('ng-binding') != -1) { 73 | bindings.push(elem); 74 | } 75 | var childBindings = elem.getElementsByClassName('ng-binding'); 76 | for (var k = 0; k < childBindings.length; ++k) { 77 | bindings.push(childBindings[k]); 78 | } 79 | } 80 | } 81 | } 82 | for (var j = 0; j < bindings.length; ++j) { 83 | var dataBinding = angular.element(bindings[j]).data('$binding'); 84 | if (dataBinding) { 85 | var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding; 86 | if (bindingName.indexOf(binding) != -1) { 87 | matches.push(bindings[j]); 88 | } 89 | } 90 | } 91 | return matches; 92 | }).apply(this, arguments); 93 | }).apply(this, arguments); } 94 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findRepeaterElement.js: -------------------------------------------------------------------------------- 1 | try { return (function anonymous() { 2 | function repeaterMatch(ngRepeat, repeater, exact) { 3 | if (exact) { 4 | return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0]. 5 | split('=')[0].trim() == repeater; 6 | } else { 7 | return ngRepeat.indexOf(repeater) != -1; 8 | } 9 | }; return (function findRepeaterElement(repeater, exact, index, binding, using, rootSelector) { 10 | var matches = []; 11 | var root = document.querySelector(rootSelector || 'body'); 12 | using = using || document; 13 | 14 | var rows = []; 15 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 16 | for (var p = 0; p < prefixes.length; ++p) { 17 | var attr = prefixes[p] + 'repeat'; 18 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 19 | attr = attr.replace(/\\/g, ''); 20 | for (var i = 0; i < repeatElems.length; ++i) { 21 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 22 | rows.push(repeatElems[i]); 23 | } 24 | } 25 | } 26 | /* multiRows is an array of arrays, where each inner array contains 27 | one row of elements. */ 28 | var multiRows = []; 29 | for (var p = 0; p < prefixes.length; ++p) { 30 | var attr = prefixes[p] + 'repeat-start'; 31 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 32 | attr = attr.replace(/\\/g, ''); 33 | for (var i = 0; i < repeatElems.length; ++i) { 34 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 35 | var elem = repeatElems[i]; 36 | var row = []; 37 | while (elem.nodeType != 8 || (elem.nodeValue && 38 | !repeaterMatch(elem.nodeValue, repeater, exact))) { 39 | if (elem.nodeType == 1) { 40 | row.push(elem); 41 | } 42 | elem = elem.nextSibling; 43 | } 44 | multiRows.push(row); 45 | } 46 | } 47 | } 48 | var row = rows[index]; 49 | var multiRow = multiRows[index]; 50 | var bindings = []; 51 | if (row) { 52 | if (angular.getTestability) { 53 | matches.push.apply( 54 | matches, 55 | angular.getTestability(root).findBindings(row, binding)); 56 | } else { 57 | if (row.className.indexOf('ng-binding') != -1) { 58 | bindings.push(row); 59 | } 60 | var childBindings = row.getElementsByClassName('ng-binding'); 61 | for (var i = 0; i < childBindings.length; ++i) { 62 | bindings.push(childBindings[i]); 63 | } 64 | } 65 | } 66 | if (multiRow) { 67 | for (var i = 0; i < multiRow.length; ++i) { 68 | var rowElem = multiRow[i]; 69 | if (angular.getTestability) { 70 | matches.push.apply( 71 | matches, 72 | angular.getTestability(root).findBindings(rowElem, binding)); 73 | } else { 74 | if (rowElem.className.indexOf('ng-binding') != -1) { 75 | bindings.push(rowElem); 76 | } 77 | var childBindings = rowElem.getElementsByClassName('ng-binding'); 78 | for (var j = 0; j < childBindings.length; ++j) { 79 | bindings.push(childBindings[j]); 80 | } 81 | } 82 | } 83 | } 84 | for (var i = 0; i < bindings.length; ++i) { 85 | var dataBinding = angular.element(bindings[i]).data('$binding'); 86 | if (dataBinding) { 87 | var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding; 88 | if (bindingName.indexOf(binding) != -1) { 89 | matches.push(bindings[i]); 90 | } 91 | } 92 | } 93 | return matches; 94 | }).apply(this, arguments); 95 | }).apply(this, arguments); } 96 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/findRepeaterRows.js: -------------------------------------------------------------------------------- 1 | try { return (function anonymous() { 2 | function repeaterMatch(ngRepeat, repeater, exact) { 3 | if (exact) { 4 | return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0]. 5 | split('=')[0].trim() == repeater; 6 | } else { 7 | return ngRepeat.indexOf(repeater) != -1; 8 | } 9 | }; return (function findRepeaterRows(repeater, exact, index, using) { 10 | using = using || document; 11 | 12 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 13 | var rows = []; 14 | for (var p = 0; p < prefixes.length; ++p) { 15 | var attr = prefixes[p] + 'repeat'; 16 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 17 | attr = attr.replace(/\\/g, ''); 18 | for (var i = 0; i < repeatElems.length; ++i) { 19 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 20 | rows.push(repeatElems[i]); 21 | } 22 | } 23 | } 24 | /* multiRows is an array of arrays, where each inner array contains 25 | one row of elements. */ 26 | var multiRows = []; 27 | for (var p = 0; p < prefixes.length; ++p) { 28 | var attr = prefixes[p] + 'repeat-start'; 29 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 30 | attr = attr.replace(/\\/g, ''); 31 | for (var i = 0; i < repeatElems.length; ++i) { 32 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 33 | var elem = repeatElems[i]; 34 | var row = []; 35 | while (elem.nodeType != 8 || 36 | !repeaterMatch(elem.nodeValue, repeater, exact)) { 37 | if (elem.nodeType == 1) { 38 | row.push(elem); 39 | } 40 | elem = elem.nextSibling; 41 | } 42 | multiRows.push(row); 43 | } 44 | } 45 | } 46 | var row = rows[index] || [], multiRow = multiRows[index] || []; 47 | return [].concat(row, multiRow); 48 | }).apply(this, arguments); 49 | }).apply(this, arguments); } 50 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/getLocationAbsUrl.js: -------------------------------------------------------------------------------- 1 | try { return (function (selector) { 2 | var el = document.querySelector(selector); 3 | if (angular.getTestability) { 4 | return angular.getTestability(el). 5 | getLocation(); 6 | } 7 | return angular.element(el).injector().get('$location').absUrl(); 8 | }).apply(this, arguments); } 9 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/getPendingHttpRequests.js: -------------------------------------------------------------------------------- 1 | try { return (function (selector) { 2 | var el = document.querySelector(selector); 3 | var $injector = angular.element(el).injector(); 4 | var $http = $injector.get('$http'); 5 | return $http.pendingRequests; 6 | }).apply(this, arguments); } 7 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/installInBrowser.js: -------------------------------------------------------------------------------- 1 | window.clientSideScripts = {waitForAngular: function (rootSelector, callback) { 2 | var el = document.querySelector(rootSelector); 3 | 4 | try { 5 | if (!window.angular) { 6 | throw new Error('angular could not be found on the window'); 7 | } 8 | if (angular.getTestability) { 9 | angular.getTestability(el).whenStable(callback); 10 | } else { 11 | if (!angular.element(el).injector()) { 12 | throw new Error('root element (' + rootSelector + ') has no injector.' + 13 | ' this may mean it is not inside ng-app.'); 14 | } 15 | angular.element(el).injector().get('$browser'). 16 | notifyWhenNoOutstandingRequests(callback); 17 | } 18 | } catch (err) { 19 | callback(err.message); 20 | } 21 | }, findBindings: function (binding, exactMatch, using, rootSelector) { 22 | var root = document.querySelector(rootSelector || 'body'); 23 | using = using || document; 24 | if (angular.getTestability) { 25 | return angular.getTestability(root). 26 | findBindings(using, binding, exactMatch); 27 | } 28 | var bindings = using.getElementsByClassName('ng-binding'); 29 | var matches = []; 30 | for (var i = 0; i < bindings.length; ++i) { 31 | var dataBinding = angular.element(bindings[i]).data('$binding'); 32 | if (dataBinding) { 33 | var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding; 34 | if (exactMatch) { 35 | var matcher = new RegExp('({|\\s|^|\\|)' + 36 | /* See http://stackoverflow.com/q/3561711 */ 37 | binding.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&') + 38 | '(}|\\s|$|\\|)'); 39 | if (matcher.test(bindingName)) { 40 | matches.push(bindings[i]); 41 | } 42 | } else { 43 | if (bindingName.indexOf(binding) != -1) { 44 | matches.push(bindings[i]); 45 | } 46 | } 47 | 48 | } 49 | } 50 | return matches; /* Return the whole array for webdriver.findElements. */ 51 | }, findRepeaterRows: function anonymous() { 52 | function repeaterMatch(ngRepeat, repeater, exact) { 53 | if (exact) { 54 | return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0]. 55 | split('=')[0].trim() == repeater; 56 | } else { 57 | return ngRepeat.indexOf(repeater) != -1; 58 | } 59 | }; return (function findRepeaterRows(repeater, exact, index, using) { 60 | using = using || document; 61 | 62 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 63 | var rows = []; 64 | for (var p = 0; p < prefixes.length; ++p) { 65 | var attr = prefixes[p] + 'repeat'; 66 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 67 | attr = attr.replace(/\\/g, ''); 68 | for (var i = 0; i < repeatElems.length; ++i) { 69 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 70 | rows.push(repeatElems[i]); 71 | } 72 | } 73 | } 74 | /* multiRows is an array of arrays, where each inner array contains 75 | one row of elements. */ 76 | var multiRows = []; 77 | for (var p = 0; p < prefixes.length; ++p) { 78 | var attr = prefixes[p] + 'repeat-start'; 79 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 80 | attr = attr.replace(/\\/g, ''); 81 | for (var i = 0; i < repeatElems.length; ++i) { 82 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 83 | var elem = repeatElems[i]; 84 | var row = []; 85 | while (elem.nodeType != 8 || 86 | !repeaterMatch(elem.nodeValue, repeater, exact)) { 87 | if (elem.nodeType == 1) { 88 | row.push(elem); 89 | } 90 | elem = elem.nextSibling; 91 | } 92 | multiRows.push(row); 93 | } 94 | } 95 | } 96 | var row = rows[index] || [], multiRow = multiRows[index] || []; 97 | return [].concat(row, multiRow); 98 | }).apply(this, arguments); 99 | }, findAllRepeaterRows: function anonymous() { 100 | function repeaterMatch(ngRepeat, repeater, exact) { 101 | if (exact) { 102 | return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0]. 103 | split('=')[0].trim() == repeater; 104 | } else { 105 | return ngRepeat.indexOf(repeater) != -1; 106 | } 107 | }; return (function findAllRepeaterRows(repeater, exact, using) { 108 | using = using || document; 109 | 110 | var rows = []; 111 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 112 | for (var p = 0; p < prefixes.length; ++p) { 113 | var attr = prefixes[p] + 'repeat'; 114 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 115 | attr = attr.replace(/\\/g, ''); 116 | for (var i = 0; i < repeatElems.length; ++i) { 117 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 118 | rows.push(repeatElems[i]); 119 | } 120 | } 121 | } 122 | for (var p = 0; p < prefixes.length; ++p) { 123 | var attr = prefixes[p] + 'repeat-start'; 124 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 125 | attr = attr.replace(/\\/g, ''); 126 | for (var i = 0; i < repeatElems.length; ++i) { 127 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 128 | var elem = repeatElems[i]; 129 | while (elem.nodeType != 8 || 130 | !repeaterMatch(elem.nodeValue, repeater, exact)) { 131 | if (elem.nodeType == 1) { 132 | rows.push(elem); 133 | } 134 | elem = elem.nextSibling; 135 | } 136 | } 137 | } 138 | } 139 | return rows; 140 | }).apply(this, arguments); 141 | }, findRepeaterElement: function anonymous() { 142 | function repeaterMatch(ngRepeat, repeater, exact) { 143 | if (exact) { 144 | return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0]. 145 | split('=')[0].trim() == repeater; 146 | } else { 147 | return ngRepeat.indexOf(repeater) != -1; 148 | } 149 | }; return (function findRepeaterElement(repeater, exact, index, binding, using, rootSelector) { 150 | var matches = []; 151 | var root = document.querySelector(rootSelector || 'body'); 152 | using = using || document; 153 | 154 | var rows = []; 155 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 156 | for (var p = 0; p < prefixes.length; ++p) { 157 | var attr = prefixes[p] + 'repeat'; 158 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 159 | attr = attr.replace(/\\/g, ''); 160 | for (var i = 0; i < repeatElems.length; ++i) { 161 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 162 | rows.push(repeatElems[i]); 163 | } 164 | } 165 | } 166 | /* multiRows is an array of arrays, where each inner array contains 167 | one row of elements. */ 168 | var multiRows = []; 169 | for (var p = 0; p < prefixes.length; ++p) { 170 | var attr = prefixes[p] + 'repeat-start'; 171 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 172 | attr = attr.replace(/\\/g, ''); 173 | for (var i = 0; i < repeatElems.length; ++i) { 174 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 175 | var elem = repeatElems[i]; 176 | var row = []; 177 | while (elem.nodeType != 8 || (elem.nodeValue && 178 | !repeaterMatch(elem.nodeValue, repeater, exact))) { 179 | if (elem.nodeType == 1) { 180 | row.push(elem); 181 | } 182 | elem = elem.nextSibling; 183 | } 184 | multiRows.push(row); 185 | } 186 | } 187 | } 188 | var row = rows[index]; 189 | var multiRow = multiRows[index]; 190 | var bindings = []; 191 | if (row) { 192 | if (angular.getTestability) { 193 | matches.push.apply( 194 | matches, 195 | angular.getTestability(root).findBindings(row, binding)); 196 | } else { 197 | if (row.className.indexOf('ng-binding') != -1) { 198 | bindings.push(row); 199 | } 200 | var childBindings = row.getElementsByClassName('ng-binding'); 201 | for (var i = 0; i < childBindings.length; ++i) { 202 | bindings.push(childBindings[i]); 203 | } 204 | } 205 | } 206 | if (multiRow) { 207 | for (var i = 0; i < multiRow.length; ++i) { 208 | var rowElem = multiRow[i]; 209 | if (angular.getTestability) { 210 | matches.push.apply( 211 | matches, 212 | angular.getTestability(root).findBindings(rowElem, binding)); 213 | } else { 214 | if (rowElem.className.indexOf('ng-binding') != -1) { 215 | bindings.push(rowElem); 216 | } 217 | var childBindings = rowElem.getElementsByClassName('ng-binding'); 218 | for (var j = 0; j < childBindings.length; ++j) { 219 | bindings.push(childBindings[j]); 220 | } 221 | } 222 | } 223 | } 224 | for (var i = 0; i < bindings.length; ++i) { 225 | var dataBinding = angular.element(bindings[i]).data('$binding'); 226 | if (dataBinding) { 227 | var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding; 228 | if (bindingName.indexOf(binding) != -1) { 229 | matches.push(bindings[i]); 230 | } 231 | } 232 | } 233 | return matches; 234 | }).apply(this, arguments); 235 | }, findRepeaterColumn: function anonymous() { 236 | function repeaterMatch(ngRepeat, repeater, exact) { 237 | if (exact) { 238 | return ngRepeat.split(' track by ')[0].split(' as ')[0].split('|')[0]. 239 | split('=')[0].trim() == repeater; 240 | } else { 241 | return ngRepeat.indexOf(repeater) != -1; 242 | } 243 | }; return (function findRepeaterColumn(repeater, exact, binding, using, rootSelector) { 244 | var matches = []; 245 | var root = document.querySelector(rootSelector || 'body'); 246 | using = using || document; 247 | 248 | var rows = []; 249 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 250 | for (var p = 0; p < prefixes.length; ++p) { 251 | var attr = prefixes[p] + 'repeat'; 252 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 253 | attr = attr.replace(/\\/g, ''); 254 | for (var i = 0; i < repeatElems.length; ++i) { 255 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 256 | rows.push(repeatElems[i]); 257 | } 258 | } 259 | } 260 | /* multiRows is an array of arrays, where each inner array contains 261 | one row of elements. */ 262 | var multiRows = []; 263 | for (var p = 0; p < prefixes.length; ++p) { 264 | var attr = prefixes[p] + 'repeat-start'; 265 | var repeatElems = using.querySelectorAll('[' + attr + ']'); 266 | attr = attr.replace(/\\/g, ''); 267 | for (var i = 0; i < repeatElems.length; ++i) { 268 | if (repeaterMatch(repeatElems[i].getAttribute(attr), repeater, exact)) { 269 | var elem = repeatElems[i]; 270 | var row = []; 271 | while (elem.nodeType != 8 || (elem.nodeValue && 272 | !repeaterMatch(elem.nodeValue, repeater, exact))) { 273 | if (elem.nodeType == 1) { 274 | row.push(elem); 275 | } 276 | elem = elem.nextSibling; 277 | } 278 | multiRows.push(row); 279 | } 280 | } 281 | } 282 | var bindings = []; 283 | for (var i = 0; i < rows.length; ++i) { 284 | if (angular.getTestability) { 285 | matches.push.apply( 286 | matches, 287 | angular.getTestability(root).findBindings(rows[i], binding)); 288 | } else { 289 | if (rows[i].className.indexOf('ng-binding') != -1) { 290 | bindings.push(rows[i]); 291 | } 292 | var childBindings = rows[i].getElementsByClassName('ng-binding'); 293 | for (var k = 0; k < childBindings.length; ++k) { 294 | bindings.push(childBindings[k]); 295 | } 296 | } 297 | } 298 | for (var i = 0; i < multiRows.length; ++i) { 299 | for (var j = 0; j < multiRows[i].length; ++j) { 300 | if (angular.getTestability) { 301 | matches.push.apply( 302 | matches, 303 | angular.getTestability(root).findBindings(multiRows[i][j], binding)); 304 | } else { 305 | var elem = multiRows[i][j]; 306 | if (elem.className.indexOf('ng-binding') != -1) { 307 | bindings.push(elem); 308 | } 309 | var childBindings = elem.getElementsByClassName('ng-binding'); 310 | for (var k = 0; k < childBindings.length; ++k) { 311 | bindings.push(childBindings[k]); 312 | } 313 | } 314 | } 315 | } 316 | for (var j = 0; j < bindings.length; ++j) { 317 | var dataBinding = angular.element(bindings[j]).data('$binding'); 318 | if (dataBinding) { 319 | var bindingName = dataBinding.exp || dataBinding[0].exp || dataBinding; 320 | if (bindingName.indexOf(binding) != -1) { 321 | matches.push(bindings[j]); 322 | } 323 | } 324 | } 325 | return matches; 326 | }).apply(this, arguments); 327 | }, findByModel: function (model, using, rootSelector) { 328 | var root = document.querySelector(rootSelector || 'body'); 329 | using = using || document; 330 | 331 | if (angular.getTestability) { 332 | return angular.getTestability(root). 333 | findModels(using, model, true); 334 | } 335 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 336 | for (var p = 0; p < prefixes.length; ++p) { 337 | var selector = '[' + prefixes[p] + 'model="' + model + '"]'; 338 | var elements = using.querySelectorAll(selector); 339 | if (elements.length) { 340 | return elements; 341 | } 342 | } 343 | }, findByOptions: function (optionsDescriptor, using) { 344 | using = using || document; 345 | 346 | var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; 347 | for (var p = 0; p < prefixes.length; ++p) { 348 | var selector = '[' + prefixes[p] + 'options="' + optionsDescriptor + '"] option'; 349 | var elements = using.querySelectorAll(selector); 350 | if (elements.length) { 351 | return elements; 352 | } 353 | } 354 | }, findByButtonText: function (searchText, using) { 355 | using = using || document; 356 | 357 | var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]'); 358 | var matches = []; 359 | for (var i = 0; i < elements.length; ++i) { 360 | var element = elements[i]; 361 | var elementText; 362 | if (element.tagName.toLowerCase() == 'button') { 363 | elementText = element.textContent || element.innerText || ''; 364 | } else { 365 | elementText = element.value; 366 | } 367 | if (elementText.trim() === searchText) { 368 | matches.push(element); 369 | } 370 | } 371 | 372 | return matches; 373 | }, findByPartialButtonText: function (searchText, using) { 374 | using = using || document; 375 | 376 | var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]'); 377 | var matches = []; 378 | for (var i = 0; i < elements.length; ++i) { 379 | var element = elements[i]; 380 | var elementText; 381 | if (element.tagName.toLowerCase() == 'button') { 382 | elementText = element.textContent || element.innerText || ''; 383 | } else { 384 | elementText = element.value; 385 | } 386 | if (elementText.indexOf(searchText) > -1) { 387 | matches.push(element); 388 | } 389 | } 390 | 391 | return matches; 392 | }, findByCssContainingText: function (cssSelector, searchText, using) { 393 | using = using || document; 394 | 395 | var elements = using.querySelectorAll(cssSelector); 396 | var matches = []; 397 | for (var i = 0; i < elements.length; ++i) { 398 | var element = elements[i]; 399 | var elementText = element.textContent || element.innerText || ''; 400 | if (elementText.indexOf(searchText) > -1) { 401 | matches.push(element); 402 | } 403 | } 404 | return matches; 405 | }, testForAngular: function (attempts, asyncCallback) { 406 | var callback = function(args) { 407 | setTimeout(function() { 408 | asyncCallback(args); 409 | }, 0); 410 | }; 411 | var check = function(n) { 412 | try { 413 | if (window.angular && window.angular.resumeBootstrap) { 414 | callback([true, null]); 415 | } else if (n < 1) { 416 | if (window.angular) { 417 | callback([false, 'angular never provided resumeBootstrap']); 418 | } else { 419 | callback([false, 'retries looking for angular exceeded']); 420 | } 421 | } else { 422 | window.setTimeout(function() {check(n - 1);}, 1000); 423 | } 424 | } catch (e) { 425 | callback([false, e]); 426 | } 427 | }; 428 | check(attempts); 429 | }, evaluate: function (element, expression) { 430 | return angular.element(element).scope().$eval(expression); 431 | }, allowAnimations: function (element, value) { 432 | var ngElement = angular.element(element); 433 | if (ngElement.allowAnimations) { 434 | // AngularDart: $testability API. 435 | return ngElement.allowAnimations(value); 436 | } else { 437 | // AngularJS 438 | var enabledFn = ngElement.injector().get('$animate').enabled; 439 | return (value == null) ? enabledFn() : enabledFn(value); 440 | } 441 | }, getLocationAbsUrl: function (selector) { 442 | var el = document.querySelector(selector); 443 | if (angular.getTestability) { 444 | return angular.getTestability(el). 445 | getLocation(); 446 | } 447 | return angular.element(el).injector().get('$location').absUrl(); 448 | }, setLocation: function (selector, url) { 449 | var el = document.querySelector(selector); 450 | if (angular.getTestability) { 451 | return angular.getTestability(el). 452 | setLocation(url); 453 | } 454 | var $injector = angular.element(el).injector(); 455 | var $location = $injector.get('$location'); 456 | var $rootScope = $injector.get('$rootScope'); 457 | 458 | if (url !== $location.url()) { 459 | $location.url(url); 460 | $rootScope.$digest(); 461 | } 462 | }, getPendingHttpRequests: function (selector) { 463 | var el = document.querySelector(selector); 464 | var $injector = angular.element(el).injector(); 465 | var $http = $injector.get('$http'); 466 | return $http.pendingRequests; 467 | }}; -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/setLocation.js: -------------------------------------------------------------------------------- 1 | try { return (function (selector, url) { 2 | var el = document.querySelector(selector); 3 | if (angular.getTestability) { 4 | return angular.getTestability(el). 5 | setLocation(url); 6 | } 7 | var $injector = angular.element(el).injector(); 8 | var $location = $injector.get('$location'); 9 | var $rootScope = $injector.get('$rootScope'); 10 | 11 | if (url !== $location.url()) { 12 | $location.url(url); 13 | $rootScope.$digest(); 14 | } 15 | }).apply(this, arguments); } 16 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/testForAngular.js: -------------------------------------------------------------------------------- 1 | try { return (function (attempts, asyncCallback) { 2 | var callback = function(args) { 3 | setTimeout(function() { 4 | asyncCallback(args); 5 | }, 0); 6 | }; 7 | var check = function(n) { 8 | try { 9 | if (window.angular && window.angular.resumeBootstrap) { 10 | callback([true, null]); 11 | } else if (n < 1) { 12 | if (window.angular) { 13 | callback([false, 'angular never provided resumeBootstrap']); 14 | } else { 15 | callback([false, 'retries looking for angular exceeded']); 16 | } 17 | } else { 18 | window.setTimeout(function() {check(n - 1);}, 1000); 19 | } 20 | } catch (e) { 21 | callback([false, e]); 22 | } 23 | }; 24 | check(attempts); 25 | }).apply(this, arguments); } 26 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/protractor/extracted/waitForAngular.js: -------------------------------------------------------------------------------- 1 | try { return (function (rootSelector, callback) { 2 | var el = document.querySelector(rootSelector); 3 | 4 | try { 5 | if (!window.angular) { 6 | throw new Error('angular could not be found on the window'); 7 | } 8 | if (angular.getTestability) { 9 | angular.getTestability(el).whenStable(callback); 10 | } else { 11 | if (!angular.element(el).injector()) { 12 | throw new Error('root element (' + rootSelector + ') has no injector.' + 13 | ' this may mean it is not inside ng-app.'); 14 | } 15 | angular.element(el).injector().get('$browser'). 16 | notifyWhenNoOutstandingRequests(callback); 17 | } 18 | } catch (err) { 19 | callback(err.message); 20 | } 21 | }).apply(this, arguments); } 22 | catch(e) { throw (e instanceof Error) ? e : new Error(e); } -------------------------------------------------------------------------------- /src/pytractor/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | import os 17 | import os.path 18 | import signal 19 | import http.server 20 | import socketserver 21 | import time 22 | 23 | from .testserver import SimpleWebServerProcess 24 | 25 | WEBSERVER_PROCESS = None 26 | 27 | 28 | def setup_package(): 29 | global WEBSERVER_PROCESS # pylint: disable=global-statement 30 | WEBSERVER_PROCESS = SimpleWebServerProcess() 31 | WEBSERVER_PROCESS.run() 32 | 33 | 34 | def teardown_package(): 35 | WEBSERVER_PROCESS.stop() 36 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/test_base.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # Copyright 2014 Konrad Podloucky 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from unittest import TestCase 17 | 18 | from pytractor.exceptions import AngularNotFoundException 19 | 20 | from .testdriver import TestDriver 21 | from .testserver import SimpleWebServerProcess 22 | 23 | 24 | class WebDriverBaseTest(TestCase): 25 | """Tests the WebDriverMixin.""" 26 | 27 | @classmethod 28 | def setUpClass(cls): 29 | cls.driver = TestDriver( 30 | 'http://localhost:{}/'.format(SimpleWebServerProcess.PORT), 31 | 'body' 32 | ) 33 | 34 | @classmethod 35 | def tearDownClass(cls): 36 | cls.driver.quit() 37 | 38 | def test_get_no_angular(self): 39 | with self.assertRaises(AngularNotFoundException): 40 | self.driver.get('index-no-angular.html') 41 | 42 | def test_get_no_angular_does_not_fail_if_ignore_synchronization_set(self): 43 | self.driver.ignore_synchronization = True 44 | self.driver.get('index-no-angular.html') 45 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/test_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Contains tests that wait for angular's processing to finish. 16 | """ 17 | 18 | from unittest import TestCase 19 | 20 | from .testdriver import TestDriver 21 | from .testserver import SimpleWebServerProcess 22 | 23 | 24 | class HelperFunctionTestCase(TestCase): 25 | """Tests for helper functions.""" 26 | 27 | @classmethod 28 | def setUpClass(cls): 29 | cls.driver = TestDriver( 30 | 'http://localhost:{}/'.format(SimpleWebServerProcess.PORT), 31 | 'body' 32 | ) 33 | 34 | @classmethod 35 | def tearDownClass(cls): 36 | cls.driver.quit() 37 | 38 | def setUp(self): 39 | self.driver.get('index.html') 40 | 41 | def test_location_abs_url_returns_absolute_url(self): 42 | url = self.driver.location_abs_url 43 | self.assertIn('/form', url) 44 | 45 | repeater_button = self.driver.find_element_by_link_text('repeater') 46 | repeater_button.click() 47 | url = self.driver.location_abs_url 48 | 49 | self.assertIn('/repeater', url) 50 | 51 | def test_set_location_navigates_to_another_url(self): 52 | self.driver.set_location('/repeater') 53 | url = self.driver.location_abs_url 54 | 55 | self.assertIn('/repeater', url) 56 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/test_locators.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # Copyright 2014 Konrad Podloucky, Michal Walkowski 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from unittest import TestCase 17 | 18 | from selenium.common.exceptions import NoSuchElementException 19 | from selenium.webdriver.remote.webelement import WebElement 20 | 21 | from .testdriver import TestDriver 22 | from .testserver import SimpleWebServerProcess 23 | 24 | 25 | class LocatorTestCase(TestCase): 26 | """Test case class for testing locators.""" 27 | 28 | @classmethod 29 | def setUpClass(cls): 30 | cls.driver = TestDriver( 31 | 'http://localhost:{}/'.format(SimpleWebServerProcess.PORT), 32 | 'body' 33 | ) 34 | 35 | @classmethod 36 | def tearDownClass(cls): 37 | cls.driver.quit() 38 | 39 | 40 | class ByBindingLocatorTest(LocatorTestCase): 41 | """Tests the locators of the WebDriverMixin that deal with bindings.""" 42 | 43 | def setUp(self): 44 | self.driver.get('index.html#/form') 45 | 46 | 47 | def test_find_element_by_binding_raises_error_if_no_element_matches(self): 48 | with self.assertRaises(NoSuchElementException): 49 | self.driver.find_element_by_binding('no-such-binding') 50 | 51 | def test_find_element_by_binding_returns_correct_element(self): 52 | element = self.driver.find_element_by_binding('greeting') 53 | self.assertIsInstance(element, WebElement) 54 | self.assertEqual(element.text, 'Hiya') 55 | 56 | def test_find_element_by_binding_finds_element_by_partial_name(self): 57 | element = self.driver.find_element_by_binding('greet') 58 | self.assertIsInstance(element, WebElement) 59 | self.assertEqual(element.text, 'Hiya') 60 | 61 | def test_find_element_by_binding_finds_element_with_ng_bind(self): 62 | element = self.driver.find_element_by_binding('username') 63 | self.assertIsInstance(element, WebElement) 64 | self.assertEqual(element.text, 'Anon') 65 | 66 | def test_find_element_by_binding_finds_element_with_ng_bind_template(self): 67 | element = self.driver.find_element_by_binding('nickname|uppercase') 68 | self.assertIsInstance(element, WebElement) 69 | self.assertEqual(element.text, '(ANNIE)') 70 | 71 | def test_find_element_by_exact_binding_finds_correct_element(self): 72 | element = self.driver.find_element_by_exact_binding('greeting') 73 | self.assertIsInstance(element, WebElement) 74 | self.assertEqual(element.text, 'Hiya') 75 | 76 | def test_find_element_by_exact_binding_needs_complete_binding_name(self): 77 | with self.assertRaises(NoSuchElementException): 78 | self.driver.find_element_by_exact_binding('greet') 79 | 80 | 81 | class ByModelLocatorTest(LocatorTestCase): 82 | def setUp(self): 83 | self.driver.get('index.html#/form') 84 | 85 | def test_find_element_by_model_finds_element_by_text_input_model(self): 86 | username = self.driver.find_element_by_model('username') 87 | name = self.driver.find_element_by_binding('username') 88 | 89 | username.clear() 90 | self.assertEqual(name.text, '') 91 | 92 | username.send_keys('Jane Doe') 93 | self.assertEqual(name.text, 'Jane Doe') 94 | 95 | username.clear() 96 | self.assertEqual(name.text, '') 97 | 98 | def test_find_element_by_model_finds_element_by_checkbox_input_model(self): 99 | shower = self.driver.find_element_by_id('shower') 100 | self.assertTrue(shower.is_displayed()) 101 | 102 | colors = self.driver.find_element_by_model('show') 103 | colors.click() 104 | 105 | shower = self.driver.find_element_by_id('shower') 106 | self.assertFalse(shower.is_displayed()) 107 | 108 | def test_find_element_by_model_finds_textarea_by_model(self): 109 | about = self.driver.find_element_by_model('aboutbox') 110 | self.assertEqual(about.get_attribute('value'), 'This is a text box') 111 | 112 | about.clear() 113 | about.send_keys('Something else to write about') 114 | 115 | self.assertEqual(about.get_attribute('value'), 116 | 'Something else to write about') 117 | 118 | def test_find_elements_by_model_find_multiple_selects_by_model(self): 119 | selects = self.driver.find_elements_by_model('dayColor.color') 120 | 121 | self.assertEqual(len(selects), 3) 122 | 123 | def test_find_element_by_model_finds_the_selected_option(self): 124 | select = self.driver.find_element_by_model('fruit') 125 | selected_option = select.find_element_by_css_selector('option:checked') 126 | 127 | self.assertEqual(selected_option.text, 'apple') 128 | 129 | def test_find_element_by_model_finds_inputs_with_alternate_attribute_forms( 130 | self 131 | ): 132 | letter_list = self.driver.find_element_by_id('letterlist') 133 | self.assertEqual(letter_list.text, '') 134 | 135 | self.driver.find_element_by_model('check.w').click() 136 | self.assertEqual(letter_list.text, 'w') 137 | 138 | self.driver.find_element_by_model('check.x').click() 139 | self.assertEqual(letter_list.text, 'wx') 140 | 141 | def test_find_elements_by_model_finds_multiple_inputs(self): 142 | inputs = self.driver.find_elements_by_model('color') 143 | 144 | self.assertEqual(len(inputs), 3) 145 | 146 | def test_find_elements_by_model_returns_empty_list_if_nothing_found(self): 147 | """find_elements_by_model() should return an empty list if no elements 148 | have been found. 149 | 150 | This tests for issue #10 which comes from protractor's findByModel.js 151 | script. 152 | """ 153 | result = self.driver.find_elements_by_model('this-model-does-not-exist') 154 | 155 | self.assertIsInstance(result, list) 156 | self.assertEqual(len(result), 0) 157 | 158 | 159 | class ByRepeaterTestCase(LocatorTestCase): 160 | def setUp(self): 161 | self.driver.get('index.html#/repeater') 162 | 163 | def test_find_elements_by_repeater_returns_correct_element(self): 164 | element = self.driver.find_elements_by_repeater('allinfo in days') 165 | self.assertEqual(len(element), 5) 166 | self.assertEqual(element[0].text, 'M Monday') 167 | self.assertEqual(element[1].text, 'T Tuesday') 168 | self.assertEqual(element[2].text, 'W Wednesday') 169 | self.assertEqual(element[3].text, 'Th Thursday') 170 | self.assertEqual(element[4].text, 'F Friday') 171 | 172 | def test_find_elements_by_repeater_returns_empty_list(self): 173 | self.assertFalse( 174 | self.driver.find_elements_by_repeater('no-such in days') 175 | ) 176 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/test_wait.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Contains tests that wait for angular's processing to finish. 16 | """ 17 | 18 | from unittest import TestCase 19 | 20 | from .testdriver import TestDriver 21 | from .testserver import SimpleWebServerProcess 22 | 23 | 24 | class AngularWaitTest(TestCase): 25 | """Test case class for testing waiting for angular.""" 26 | 27 | @classmethod 28 | def setUpClass(cls): 29 | cls.driver = TestDriver( 30 | 'http://localhost:{}/'.format(SimpleWebServerProcess.PORT), 31 | 'body' 32 | ) 33 | 34 | @classmethod 35 | def tearDownClass(cls): 36 | cls.driver.quit() 37 | 38 | def setUp(self): 39 | self.driver.get('index.html#/async') 40 | 41 | def test_waits_for_http_calls(self): 42 | status = self.driver.find_element_by_binding('slowHttpStatus') 43 | button = self.driver.find_element_by_css_selector( 44 | '[ng-click="slowHttp()"]' 45 | ) 46 | self.assertEqual(status.text, 'not started') 47 | 48 | button.click() 49 | 50 | self.assertEqual(status.text, 'done') 51 | 52 | def test_waits_for_long_javascript_execution(self): 53 | status = self.driver.find_element_by_binding('slowFunctionStatus') 54 | button = self.driver.find_element_by_css_selector( 55 | '[ng-click="slowFunction()"]' 56 | ) 57 | self.assertEqual(status.text, 'not started') 58 | 59 | button.click() 60 | 61 | self.assertEqual(status.text, 'done') 62 | 63 | def test_does_not_wait_for_timeout(self): 64 | status = self.driver.find_element_by_binding('slowTimeoutStatus') 65 | button = self.driver.find_element_by_css_selector( 66 | '[ng-click="slowTimeout()"]' 67 | ) 68 | self.assertEqual(status.text, 'not started') 69 | 70 | button.click() 71 | 72 | self.assertEqual(status.text, 'pending...') 73 | 74 | def test_waits_for_timeout_service(self): 75 | status = self.driver.find_element_by_binding( 76 | 'slowAngularTimeoutStatus' 77 | ) 78 | button = self.driver.find_element_by_css_selector( 79 | '[ng-click="slowAngularTimeout()"]' 80 | ) 81 | self.assertEqual(status.text, 'not started') 82 | 83 | button.click() 84 | 85 | self.assertEqual(status.text, 'done') 86 | 87 | def test_waits_for_timeout_service_then_a_promise(self): 88 | status = self.driver.find_element_by_binding( 89 | 'slowAngularTimeoutPromiseStatus' 90 | ) 91 | button = self.driver.find_element_by_css_selector( 92 | '[ng-click="slowAngularTimeoutPromise()"]' 93 | ) 94 | self.assertEqual(status.text, 'not started') 95 | 96 | button.click() 97 | 98 | self.assertEqual(status.text, 'done') 99 | 100 | def test_waits_for_long_http_call_then_a_promise(self): 101 | status = self.driver.find_element_by_binding('slowHttpPromiseStatus') 102 | button = self.driver.find_element_by_css_selector( 103 | '[ng-click="slowHttpPromise()"]' 104 | ) 105 | self.assertEqual(status.text, 'not started') 106 | 107 | button.click() 108 | 109 | self.assertEqual(status.text, 'done') 110 | 111 | def test_waits_for_slow_routing_changes(self): 112 | status = self.driver.find_element_by_binding('routingChangeStatus') 113 | button = self.driver.find_element_by_css_selector( 114 | '[ng-click="routingChange()"]' 115 | ) 116 | self.assertEqual(status.text, 'not started') 117 | 118 | button.click() 119 | 120 | self.assertIn('polling mechanism', self.driver.page_source) 121 | 122 | def test_waits_for_slow_ng_include_templates_to_load(self): 123 | status = self.driver.find_element_by_css_selector('.included') 124 | button = self.driver.find_element_by_css_selector( 125 | '[ng-click="changeTemplateUrl()"]' 126 | ) 127 | self.assertEqual(status.text, 'fast template contents') 128 | 129 | button.click() 130 | 131 | # need to refetch status as the element has been removed from the DOM 132 | status = self.driver.find_element_by_css_selector('.included') 133 | self.assertEqual(status.text, 'slow template contents') 134 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/test_webdriver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Simple tests for Firefox and Chrome drivers. We just see if the drivers can 17 | be instantiated and run a simple test. 18 | """ 19 | import unittest 20 | 21 | from selenium.webdriver.remote.webelement import WebElement 22 | 23 | # pylint: disable=no-name-in-module 24 | from pytractor.webdriver import Firefox, Chrome 25 | # pylint: enable=no-name-in-module 26 | 27 | from . import SimpleWebServerProcess 28 | 29 | 30 | class WebDriverTestBase(object): 31 | 32 | @classmethod 33 | def setUpClass(cls): 34 | cls.driver = cls.driver_class( 35 | 'http://localhost:{}/'.format(SimpleWebServerProcess.PORT), 36 | 'body' 37 | ) 38 | 39 | @classmethod 40 | def tearDownClass(cls): 41 | cls.driver.quit() 42 | 43 | def test_find_element_by_binding(self): 44 | self.driver.get('index.html#/form') 45 | element = self.driver.find_element_by_binding('greeting') 46 | self.assertIsInstance(element, WebElement) 47 | self.assertEqual(element.text, 'Hiya') 48 | 49 | 50 | class FirefoxWebDriverTest(WebDriverTestBase, unittest.TestCase): 51 | driver_class = Firefox 52 | 53 | 54 | class ChromeWebDriverTest(WebDriverTestBase, unittest.TestCase): 55 | driver_class = Chrome 56 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2014 Google, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/alt_root_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My AngularJS App 6 | 7 | 8 | 9 | 10 |
11 | {{1 + 2}} 12 |
13 | 14 |
15 |
16 | {{1 + 2}} 17 |
18 | 19 | 25 | 26 |
27 |
Angular seed app: v
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/animation/animation.css: -------------------------------------------------------------------------------- 1 | #animationTop .animate-if { 2 | background:white; 3 | border:1px solid black; 4 | padding:10px; 5 | } 6 | 7 | #animationTop .animate-if.ng-enter, 8 | #animationTop .animate-if.ng-leave { 9 | -webkit-transition:all linear 1s; 10 | -moz-transition:all linear 1s; 11 | -o-transition:all linear 1s; 12 | transition:all linear 1s; 13 | 14 | } 15 | 16 | #animationTop .animate-if.ng-enter, 17 | #animationTop .animate-if.ng-leave.ng-leave-active { 18 | opacity:0; 19 | } 20 | 21 | #animationTop .animate-if.ng-leave, 22 | #animationTop .animate-if.ng-enter.ng-enter-active { 23 | opacity:1; 24 | } 25 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/animation/animation.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 |
8 | I exist! 9 |
10 |
11 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/animation/animation.js: -------------------------------------------------------------------------------- 1 | function AnimationCtrl($scope) { 2 | $scope.checked = true; 3 | } 4 | 5 | AnimationCtrl.$inject = ['$scope']; 6 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/app.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | 3 | .menu { 4 | list-style: none; 5 | border-bottom: 0.1em solid black; 6 | margin-bottom: 2em; 7 | padding: 0 0 0.5em; 8 | } 9 | 10 | .menu:before { 11 | content: "["; 12 | } 13 | 14 | .menu:after { 15 | content: "]"; 16 | } 17 | 18 | .menu > li { 19 | display: inline; 20 | } 21 | 22 | .menu > li:before { 23 | content: "|"; 24 | padding-right: 0.3em; 25 | } 26 | 27 | .menu > li:nth-child(1):before { 28 | content: ""; 29 | padding: 0; 30 | } 31 | 32 | .ng-scope { 33 | background-color: rgba(0,0,0,.05); 34 | } 35 | 36 | .ng-binding { 37 | border: 1px solid rgba(50, 200, 50, .8); 38 | } 39 | 40 | #chat-box { 41 | width: 300px; 42 | height: 200px; 43 | padding: 25px; 44 | border: 2px solid; 45 | margin: 25px; 46 | overflow: scroll; 47 | } 48 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Declare app level module which depends on filters, and services 5 | angular.module('myApp', ['ngAnimate', 'ngRoute', 'myApp.appVersion']). 6 | config(['$routeProvider', function($routeProvider) { 7 | $routeProvider.when('/repeater', {templateUrl: 'repeater/repeater.html', controller: RepeaterCtrl}); 8 | $routeProvider.when('/bindings', {templateUrl: 'bindings/bindings.html', controller: BindingsCtrl}); 9 | $routeProvider.when('/form', {templateUrl: 'form/form.html', controller: FormCtrl}); 10 | $routeProvider.when('/async', {templateUrl: 'async/async.html', controller: AsyncCtrl}); 11 | $routeProvider.when('/conflict', {templateUrl: 'conflict/conflict.html', controller: ConflictCtrl}); 12 | $routeProvider.when('/polling', {templateUrl: 'polling/polling.html', controller: PollingCtrl}); 13 | $routeProvider.when('/animation', {templateUrl: 'animation/animation.html', controller: AnimationCtrl}); 14 | $routeProvider.when('/interaction', {templateUrl: 'interaction/interaction.html', controller: InteractionCtrl}); 15 | $routeProvider.when('/shadow', {templateUrl: 'shadow/shadow.html', controller: ShadowCtrl}); 16 | $routeProvider.when('/slowloader', { 17 | templateUrl: 'polling/polling.html', 18 | controller: PollingCtrl, 19 | resolve: { 20 | slow: function($timeout) { 21 | return $timeout(function() {}, 5000); 22 | } 23 | } 24 | }); 25 | $routeProvider.otherwise({redirectTo: '/form'}); 26 | }]); 27 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/async/async.html: -------------------------------------------------------------------------------- 1 | Slow things that can happen: 2 |
    3 |
  • 4 | 5 | 6 |
  • 7 |
  • 8 | 9 | 10 |
  • 11 |
  • 12 | 13 | 14 |
  • 15 |
  • 16 | 17 | 18 |
  • 19 |
  • 20 | 21 | 22 |
  • 23 |
  • 24 | 25 | 26 |
  • 27 |
  • 28 | 29 | 30 |
  • 31 |
  • 32 | 33 |
    34 |
  • 35 |
36 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/async/async.js: -------------------------------------------------------------------------------- 1 | function AsyncCtrl($scope, $http, $timeout, $location) { 2 | $scope.slowHttpStatus = 'not started'; 3 | $scope.slowFunctionStatus = 'not started'; 4 | $scope.slowTimeoutStatus = 'not started'; 5 | $scope.slowAngularTimeoutStatus = 'not started'; 6 | $scope.slowAngularTimeoutPromiseStatus = 'not started'; 7 | $scope.slowHttpPromiseStatus = 'not started'; 8 | $scope.routingChangeStatus = 'not started'; 9 | $scope.templateUrl = 'fastTemplateUrl'; 10 | 11 | $scope.slowHttp = function() { 12 | $scope.slowHttpStatus = 'pending...'; 13 | $http({method: 'GET', url: 'slowcall'}).success(function() { 14 | $scope.slowHttpStatus = 'done'; 15 | }); 16 | }; 17 | 18 | $scope.slowFunction = function() { 19 | $scope.slowFunctionStatus = 'pending...'; 20 | for (var i = 0, t = 0; i < 500000000; ++i) { 21 | t++; 22 | } 23 | $scope.slowFunctionStatus = 'done'; 24 | }; 25 | 26 | $scope.slowTimeout = function() { 27 | $scope.slowTimeoutStatus = 'pending...'; 28 | window.setTimeout(function() { 29 | $scope.$apply(function() { 30 | $scope.slowTimeoutStatus = 'done'; 31 | }); 32 | }, 5000); 33 | }; 34 | 35 | $scope.slowAngularTimeout = function() { 36 | $scope.slowAngularTimeoutStatus = 'pending...'; 37 | $timeout(function() { 38 | $scope.slowAngularTimeoutStatus = 'done'; 39 | }, 5000); 40 | }; 41 | 42 | $scope.slowAngularTimeoutPromise = function() { 43 | $scope.slowAngularTimeoutPromiseStatus = 'pending...'; 44 | $timeout(function() { 45 | // intentionally empty 46 | }, 5000).then(function() { 47 | $scope.slowAngularTimeoutPromiseStatus = 'done'; 48 | }); 49 | }; 50 | 51 | $scope.slowHttpPromise = function() { 52 | $scope.slowHttpPromiseStatus = 'pending...'; 53 | $http({method: 'GET', url: 'slowcall'}).success(function() { 54 | // intentionally empty 55 | }).then(function() { 56 | $scope.slowHttpPromiseStatus = 'done'; 57 | }); 58 | }; 59 | 60 | $scope.routingChange = function() { 61 | $scope.routingChangeStatus = 'pending...'; 62 | $location.url('slowloader'); 63 | }; 64 | 65 | $scope.changeTemplateUrl = function() { 66 | $scope.templateUrl = 'slowTemplateUrl'; 67 | }; 68 | } 69 | 70 | AsyncCtrl.$inject = ['$scope', '$http', '$timeout', '$location']; 71 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/bindings/bindings.html: -------------------------------------------------------------------------------- 1 |
Details for: 2 | 4 |
5 |
{{planet.name}}
6 |
Radius: {{getRadiusKm()}}km
7 |
Moons: {{moon}}
8 |
9 |
10 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/bindings/bindings.js: -------------------------------------------------------------------------------- 1 | function BindingsCtrl($scope) { 2 | $scope.planets = [ 3 | { name: 'Mercury', 4 | radius: 1516 5 | }, 6 | { name: 'Venus', 7 | radius: 3760 8 | }, 9 | { name: 'Earth', 10 | radius: 3959, 11 | moons: ['Luna'] 12 | }, 13 | { name: 'Mars', 14 | radius: 2106, 15 | moons: ['Phobos', 'Deimos'] 16 | }, 17 | { name: 'Jupiter', 18 | radius: 43441, 19 | moons: ['Europa', 'Io', 'Ganymede', 'Castillo'] 20 | }, 21 | { name: 'Saturn', 22 | radius: 36184, 23 | moons: ['Titan', 'Rhea', 'Iapetus', 'Dione'] 24 | }, 25 | { name: 'Uranus', 26 | radius: 15759, 27 | moons: ['Titania', 'Oberon', 'Umbriel', 'Ariel'] 28 | }, 29 | { name: 'Neptune', 30 | radius: 15299, 31 | moons: ['Triton', 'Proteus', 'Nereid', 'Larissa'] 32 | } 33 | ]; 34 | 35 | $scope.planet = $scope.planets[0]; 36 | 37 | $scope.getRadiusKm = function() { 38 | return $scope.planet.radius * 0.6213; 39 | }; 40 | } 41 | 42 | BindingsCtrl.$inject = ['$scope']; 43 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/components/app-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('myApp.appVersion', []). 4 | value('version', '0.1'). 5 | directive('appVersion', ['version', function(version) { 6 | return function(scope, elm, attrs) { 7 | elm.text(version); 8 | }; 9 | }]); 10 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/conflict/conflict.html: -------------------------------------------------------------------------------- 1 | Outer: {{item.reusedBinding}} 2 | Other other: {{item.alsoReused}} 3 | 4 |
5 |
6 | Inner: {{item.reusedBinding}} 7 | Inner other: {{item.alsoReused}} 8 |
9 |
10 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/conflict/conflict.js: -------------------------------------------------------------------------------- 1 | function ConflictCtrl($scope) { 2 | $scope.item = { 3 | reusedBinding: 'outer', 4 | alsoReused: 'outerbarbaz' 5 | }; 6 | 7 | $scope.wrapper = [{ 8 | reusedBinding: 'inner', 9 | alsoReused: 'innerbarbaz' 10 | }]; 11 | } 12 | ConflictCtrl.$inject = ['$scope']; 13 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kpodl/pytractor/04471229093c8c9dff210f7f238d8dd8a3a9d26a/src/pytractor/tests/functional/testapp/favicon.ico -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/form/form.html: -------------------------------------------------------------------------------- 1 |

Forms using different types of input

2 | 3 |
4 |

Bindings

5 | {{greeting}} 6 | 7 | 8 |
9 | 10 |
11 |

Text

12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |

Textarea

20 | 21 |
22 | 23 |
24 |

Multiple input radio

25 |
Color
26 | blue
27 | green
28 | red
29 |
30 |
31 | 32 |
33 |

Selects

34 |
35 | 36 |
37 | 38 | 41 | Fruit: {{fruit}} 42 |
43 | 44 |
45 |

Checkboxes

46 | Show? 47 | Shown!! 48 | 49 | Disable? 50 | 51 | 52 | W 53 | X 54 | 55 | {{check.w}}{{check.x}} 56 |
57 | 58 |
59 |

Drag and Drop

60 | 61 |
62 | 63 |
64 |

Alert trigger

65 | 66 |
67 | 68 |
69 |

Buttons

70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 |
79 |

Inner text

80 |
    81 |
  • big dog
  • 82 |
  • small dog
  • 83 |
  • other dog
  • 84 |
  • big cat
  • 85 |
  • small cat
  • 86 |
87 |
88 | 89 |
90 |

Inputs

91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 |
102 | 103 |
104 |

Transformed text

105 |
Uppercase
106 |
Lowercase
107 |
capitalize
108 |
109 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/form/form.js: -------------------------------------------------------------------------------- 1 | function FormCtrl($scope, $window) { 2 | $scope.greeting = 'Hiya'; 3 | $scope.username = 'Anon'; 4 | $scope.nickname = 'annie'; 5 | $scope.aboutbox = 'This is a text box'; 6 | $scope.color = 'blue'; 7 | $scope.show = true; 8 | 9 | $scope.colors = ['red', 'green', 'blue']; 10 | $scope.dayColors = [{day: 'Mon', color: 'red'}, {day: 'Tue', color: 'green'}, {day: 'Wed', color: 'blue'}]; 11 | 12 | $scope.fruit = ''; 13 | $scope.defaultFruit = 'apple'; 14 | $scope.fruits = ['pear', 'peach', 'banana']; 15 | 16 | $scope.doAlert = function() { 17 | $window.alert('Hello'); 18 | }; 19 | } 20 | FormCtrl.$inject = ['$scope', '$window']; 21 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/index-no-angular.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test App -- Without Angular.js 6 | 7 | 8 |

There is no angular here.

9 | 10 | 11 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My AngularJS App 6 | 7 | 8 | 9 | 10 | 21 | 22 |
23 | 24 |
Angular seed app: v
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/interaction/interaction.html: -------------------------------------------------------------------------------- 1 |
2 |

A simple chat system

3 | 4 |
5 | 6 | 7 |
8 | 9 |
10 |
{{msg}}
11 |
12 |
13 | 14 | 15 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/interaction/interaction.js: -------------------------------------------------------------------------------- 1 | function InteractionCtrl($scope, $interval, $http) { 2 | 3 | $scope.messages = []; 4 | $scope.message = ''; 5 | $scope.user = ''; 6 | $scope.userInput = ''; 7 | 8 | $scope.sendUser = function() { 9 | $scope.user = $scope.userInput; 10 | }; 11 | 12 | var loadMessages = function() { 13 | $http.get('chat?q=chatMessages'). 14 | success(function(data) { 15 | $scope.messages = data ? data : []; 16 | }). 17 | error(function(err) { 18 | $scope.messages = ['server request failed with: ' + err]; 19 | }); 20 | }; 21 | 22 | $scope.sendMessage = function() { 23 | var msg = $scope.user + ': ' + $scope.message; 24 | $scope.messages.push(msg); 25 | $scope.message = ''; 26 | 27 | var data = { 28 | key: 'newChatMessage', 29 | value: msg 30 | }; 31 | $http.post('chat', data); 32 | }; 33 | 34 | $scope.clearMessages = function() { 35 | $scope.messages = []; 36 | 37 | var data = { 38 | key: 'clearChatMessages' 39 | }; 40 | $http.post('chat', data); 41 | }; 42 | 43 | $interval(function() { 44 | loadMessages(); 45 | }, 100); 46 | } 47 | InteractionCtrl.$inject = ['$scope', '$interval', '$http']; 48 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/lib/angular: -------------------------------------------------------------------------------- 1 | angular_v1.3.13/ -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/lib/angular_v1.2.9/angular-animate.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.9 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(H,f,s){'use strict';f.module("ngAnimate",["ng"]).factory("$$animateReflow",["$window","$timeout",function(f,D){var c=f.requestAnimationFrame||f.webkitRequestAnimationFrame||function(c){return D(c,10,!1)},x=f.cancelAnimationFrame||f.webkitCancelAnimationFrame||function(c){return D.cancel(c)};return function(k){var f=c(k);return function(){x(f)}}}]).config(["$provide","$animateProvider",function(S,D){function c(c){for(var f=0;f=t&&d>=n&&y()}var m=a.data(q),h=c(a);if(-1!=h.className.indexOf(e)&&m){var k=m.timings,l=m.stagger,n=m.maxDuration,r=m.activeClassName,t=Math.max(k.transitionDelay,k.animationDelay)* 19 | aa,w=Date.now(),v=U+" "+T,u=m.itemIndex,p="",s=[];if(0=C&&b>=x&&c()}var m=g(e);a=e.data("$$ngAnimateCSS3Data");if(-1!=m.getAttribute("class").indexOf(b)&&a){var k="",t="";n(b.split(" "),function(a, 26 | b){var e=(0 2 | 3 | Test Application Login 4 | 18 | 19 | 20 |
Login
21 |
22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/polling/polling.html: -------------------------------------------------------------------------------- 1 |
This view shows a controller which uses a polling mechanism to 2 | contact the server. It is constantly using angular's $timeout.
3 | 4 |
{{count}}
5 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/polling/polling.js: -------------------------------------------------------------------------------- 1 | function PollingCtrl($scope, $timeout) { 2 | $scope.count = 0; 3 | 4 | $scope.startPolling = function() { 5 | function poll() { 6 | $timeout(function() { 7 | $scope.count++; 8 | poll(); 9 | }, 1000); 10 | }; 11 | 12 | poll(); 13 | }; 14 | } 15 | PollingCtrl.$inject = ['$scope', '$timeout']; 16 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/repeater/repeater.html: -------------------------------------------------------------------------------- 1 |

A series of repeaters used in tests.

2 |
  • 3 | {{allinfo.initial}} 4 | {{allinfo.name}} 5 |
6 |
  • 7 | {{baz.initial}} 8 |
9 |
  • 10 | {{baz.initial}} 11 |
12 |
  • 13 | {{day.initial}} 14 |
15 |
  • 16 | {{bar.initial}} 17 |
18 |
19 | {{bloop.initial}} 20 |
21 | - 22 |
23 | {{bloop.name}} 24 |
25 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/repeater/repeater.js: -------------------------------------------------------------------------------- 1 | function RepeaterCtrl($scope) { 2 | $scope.days = [ 3 | {initial: 'M', name: 'Monday'}, 4 | {initial: 'T', name: 'Tuesday'}, 5 | {initial: 'W', name: 'Wednesday'}, 6 | {initial: 'Th', name: 'Thursday'}, 7 | {initial: 'F', name: 'Friday'} 8 | ]; 9 | } 10 | 11 | RepeaterCtrl.$inject = ['$scope']; 12 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/scripts/web-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var express = require('express'); 4 | var optimist = require('optimist'); 5 | var util = require('util'); 6 | var path = require('path'); 7 | var env = require('../../spec/environment.js'); 8 | 9 | var testApp = express(); 10 | var DEFAULT_PORT = process.env.HTTP_PORT || env.webServerDefaultPort; 11 | var testAppDir = path.resolve(__dirname, '..'); 12 | var defaultAngular = require(path.join(testAppDir, 'lib/angular_version.js')); 13 | 14 | var argv = optimist.describe('port', 'port'). 15 | default('port', DEFAULT_PORT). 16 | describe('ngversion', 'version of AngularJS to use'). 17 | default('ngversion', defaultAngular). 18 | argv; 19 | 20 | var angularDir = path.join(testAppDir, 'lib/angular_v' + argv.ngversion); 21 | 22 | var main = function() { 23 | var port = argv.port; 24 | testApp.listen(port); 25 | util.puts(["Starting express web server in", testAppDir ,"on port", port]. 26 | join(" ")); 27 | }; 28 | 29 | var storage = {}; 30 | var testMiddleware = function(req, res, next) { 31 | if (req.path == '/fastcall') { 32 | res.send(200, 'done'); 33 | } else if (req.path == '/slowcall') { 34 | setTimeout(function() { 35 | res.send(200, 'finally done'); 36 | }, 5000); 37 | } else if (req.path == '/fastTemplateUrl') { 38 | res.send(200, 'fast template contents'); 39 | } else if (req.path == '/slowTemplateUrl') { 40 | setTimeout(function() { 41 | res.send(200, 'slow template contents'); 42 | }, 5000); 43 | } else if (req.path == '/chat') { 44 | if (req.method === 'GET') { 45 | var value; 46 | if (req.query.q) { 47 | value = storage[req.query.q]; 48 | res.send(200, value); 49 | } else { 50 | res.send(400, 'must specify query'); 51 | } 52 | } else if (req.method === 'POST') { 53 | if (req.body.key == 'newChatMessage') { 54 | if (!storage['chatMessages']) { 55 | storage['chatMessages'] = []; 56 | } 57 | storage['chatMessages'].push(req.body.value); 58 | res.send(200); 59 | } else if (req.body.key == 'clearChatMessages') { 60 | storage['chatMessages'] = []; 61 | res.send(200); 62 | } else { 63 | res.send(400, 'Unknown command'); 64 | } 65 | } else { 66 | res.send(400, 'only accepts GET/POST'); 67 | } 68 | } else { 69 | return next(); 70 | } 71 | }; 72 | 73 | testApp.configure(function() { 74 | testApp.use('/lib/angular', express.static(angularDir)); 75 | testApp.use(express.static(testAppDir)); 76 | testApp.use(express.json()); 77 | testApp.use(testMiddleware); 78 | }); 79 | 80 | main(); 81 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/shadow/shadow.html: -------------------------------------------------------------------------------- 1 |

Elements in shadow DOM

2 |

Inspired by ChromeDriver's page for shadow dom webdriver tests. The page has a shadow root that in turn contains two shadow roots. So we can check behaviour with both nested roots and younger/older sibling roots. 3 |

4 |
5 | original content 6 |
7 |
8 | 17 | 26 | 43 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testapp/shadow/shadow.js: -------------------------------------------------------------------------------- 1 | function ShadowCtrl($scope) { 2 | // This is terrible Angular.js style, do not put DOM manipulation inside 3 | // controllers like this. 4 | var parentShadowRoot = document.querySelector('#innerDiv') 5 | .createShadowRoot(); 6 | parentShadowRoot.appendChild(document.querySelector('#parentTemplate') 7 | .content.cloneNode(true)); 8 | var olderShadowRoot = parentShadowRoot.querySelector("#parentDiv") 9 | .createShadowRoot(); 10 | olderShadowRoot.appendChild(document.querySelector('#olderChildTemplate') 11 | .content.cloneNode(true)); 12 | var youngerShadowRoot = parentShadowRoot.querySelector("#parentDiv") 13 | .createShadowRoot(); 14 | youngerShadowRoot.appendChild(document.querySelector('#youngerChildTemplate') 15 | .content.cloneNode(true)); 16 | } 17 | 18 | RepeaterCtrl.$inject = ['$scope']; 19 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testdriver.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # Copyright 2014 Konrad Podloucky 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | """ 16 | The webdriver used for most functional tests. 17 | """ 18 | 19 | from selenium import webdriver 20 | 21 | from pytractor.mixins import WebDriverMixin 22 | 23 | 24 | class TestDriver(WebDriverMixin, webdriver.Firefox): 25 | """We use the Firefox driver with our mixin for these tests.""" 26 | pass 27 | -------------------------------------------------------------------------------- /src/pytractor/tests/functional/testserver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | This is the web server that serves the angular app that we use for testing. 16 | It is started by setup_package() in __init__.py 17 | """ 18 | 19 | import logging 20 | import os 21 | import signal 22 | import http.server 23 | import socketserver 24 | import time 25 | 26 | PORT = 8000 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | 31 | class TestServerHandler(http.server.SimpleHTTPRequestHandler): 32 | """ 33 | The handler for the web server. It has the same functionality as the 34 | server for protractor's test app. 35 | """ 36 | def send_text_response(self, text): 37 | self.send_response(200) 38 | self.send_header("Content-type", 'text/plain') 39 | self.send_header("Content-Length", len(text)) 40 | self.end_headers() 41 | self.wfile.write(text.encode('utf-8')) 42 | 43 | def do_GET(self): 44 | if self.path == '/fastcall': 45 | self.send_text_response('done') 46 | elif self.path == '/slowcall': 47 | time.sleep(5) 48 | self.send_text_response('finally done') 49 | elif self.path == '/fastTemplateUrl': 50 | self.send_text_response('fast template contents') 51 | elif self.path == '/slowTemplateUrl': 52 | time.sleep(5) 53 | self.send_text_response('slow template contents') 54 | else: 55 | http.server.SimpleHTTPRequestHandler.do_GET(self) 56 | 57 | def log_message(self, msg_format, *args): 58 | """Use python logging to avoid lots of output during testing.""" 59 | logger.info("TESTSERVER: %s - - [%s] %s\n" % 60 | (self.client_address[0], 61 | self.log_date_time_string(), 62 | msg_format % args)) 63 | 64 | 65 | class SimpleWebServerProcess(object): 66 | """ 67 | A simple webserver for serving pages for testing. 68 | """ 69 | HOST = 'localhost' 70 | PORT = 9999 71 | APP_DIR = 'testapp' 72 | _pid = None 73 | 74 | def run(self): 75 | self._pid = os.fork() 76 | if self._pid == 0: 77 | self.start_server() 78 | else: 79 | logger.debug('Started webserver child as pid {} on' 80 | ' port {}'.format(self._pid, self.PORT)) 81 | # wait 5 seconds for server to start 82 | time.sleep(5) 83 | 84 | def start_server(self): 85 | module_path = __file__ 86 | server_path = os.path.join(os.path.dirname(module_path), self.APP_DIR) 87 | logger.debug('Starting webserver for path {} on' 88 | ' port {}'.format(server_path, self.PORT)) 89 | os.chdir(server_path) 90 | handler = TestServerHandler 91 | socketserver.TCPServer.allow_reuse_address = True 92 | httpd = socketserver.TCPServer((self.HOST, self.PORT), handler) 93 | httpd.serve_forever() 94 | 95 | def stop(self): 96 | if self._pid != 0: 97 | logger.debug('Sending SIGTERM to webserver child with' 98 | ' pid {}'.format(self._pid)) 99 | os.kill(self._pid, signal.SIGTERM) 100 | os.waitpid(self._pid, 0) 101 | 102 | if __name__ == '__main__': 103 | logging.basicConfig(level=logging.DEBUG) 104 | # start blocking server, do not fork into the background 105 | process = SimpleWebServerProcess() 106 | process.start_server() 107 | -------------------------------------------------------------------------------- /src/pytractor/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /src/pytractor/tests/unit/test_mixins.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | 17 | from mock import MagicMock, patch, PropertyMock, DEFAULT 18 | 19 | from selenium.common.exceptions import NoSuchElementException 20 | 21 | from pytractor.exceptions import AngularNotFoundException 22 | from pytractor.mixins import (WebDriverMixin, angular_wait_required, 23 | CLIENT_SCRIPTS_DIR) 24 | 25 | 26 | try: # FIXME: find another way 27 | import __builtin__ 28 | super_str = '__builtin__.super' 29 | except ImportError: 30 | super_str = 'builtins.super' 31 | 32 | 33 | class AngularWaitRequiredDecoratorTest(unittest.TestCase): 34 | @angular_wait_required 35 | def wrapped_function(self, *args, **kwargs): 36 | return self.check_function(*args, **kwargs) 37 | 38 | def check_function(self, *args, **kwargs): 39 | pass 40 | 41 | def test_angular_wait_required(self): 42 | with patch.multiple(self, wait_for_angular=DEFAULT, 43 | check_function=DEFAULT, create=True) as patched: 44 | mock_wait_for_angular = patched['wait_for_angular'] 45 | mock_check_function = patched['check_function'] 46 | mock_arg = MagicMock() 47 | mock_kwarg = MagicMock() 48 | result = self.wrapped_function(mock_arg, kwarg=mock_kwarg) 49 | # result should be the result of the wrapped function 50 | self.assertIs(result, mock_check_function.return_value) 51 | # wait_for_angular() should have been called 52 | mock_wait_for_angular.assert_called_once_with() 53 | # the check function should have been called 54 | mock_check_function.assert_called_once_with(mock_arg, 55 | kwarg=mock_kwarg) 56 | 57 | 58 | class WebDriverMixinConstructorTest(unittest.TestCase): 59 | class ConstructorTester(object): 60 | """Class for checking calls to __init__""" 61 | def __init__(self, *args, **kwargs): 62 | self.constructor_called(*args, **kwargs) 63 | 64 | def constructor_called(self, *args, **kwargs): 65 | """ 66 | This function will be called by __init__. We can hook onto this 67 | to check for __init__ calls. 68 | """ 69 | pass 70 | 71 | class TestDriver(WebDriverMixin, ConstructorTester): 72 | pass 73 | 74 | def test_constructor(self): 75 | base_url = 'BASEURL' 76 | root_element = 'ROOTEL' 77 | test_timeout = 'TESTTIMEOUT' 78 | script_timeout = 'SCRIPTTIMEOUT' 79 | 80 | with patch.object( 81 | self.ConstructorTester, 'constructor_called' 82 | ) as mock_constructor_called, patch.object( 83 | self.TestDriver, 'set_script_timeout', create=True 84 | ) as mock_set_script_timeout: 85 | instance = self.TestDriver(base_url, root_element, 86 | test_timeout=test_timeout, 87 | script_timeout=script_timeout) 88 | 89 | # verify that properties have been set 90 | self.assertIs(instance._base_url, base_url) 91 | self.assertIs(instance._root_element, root_element) 92 | self.assertIs(instance._test_timeout, test_timeout) 93 | # verify that the super class' constructor has been called 94 | mock_constructor_called.assert_called_once_with() 95 | # verify that set_script_timeout has been called 96 | mock_set_script_timeout.assert_called_once_with(script_timeout) 97 | 98 | 99 | class WebDriverMixinTest(unittest.TestCase): 100 | def setUp(self): 101 | set_script_timeout_patcher = patch.object( 102 | WebDriverMixin, 'set_script_timeout', create=True 103 | ) 104 | self.mock_set_script_timeout = set_script_timeout_patcher.start() 105 | self.addCleanup(set_script_timeout_patcher.stop) 106 | self.mock_root_element = MagicMock() 107 | self.instance = WebDriverMixin('http://localhost', 108 | self.mock_root_element) 109 | 110 | @patch('pytractor.mixins.resource_string') 111 | def verify__execute_client_script_call(self, async, mock_resource_string): 112 | with patch.multiple( 113 | self.instance, 114 | execute_async_script=DEFAULT, execute_script=DEFAULT, 115 | create=True 116 | ) as mock_execute: 117 | (mock_execute_script, 118 | mock_execute_async_script) = [mock_execute.get(func_name) 119 | for func_name in 120 | ('execute_script', 121 | 'execute_async_script')] 122 | mock_arg = MagicMock() 123 | result = self.instance._execute_client_script('SCRIPT', mock_arg, 124 | async=async) 125 | # the script was read correctly with resource_string() 126 | mock_resource_string.assert_called_once_with( 127 | 'pytractor.mixins', 128 | '{}/{}.js'.format(CLIENT_SCRIPTS_DIR, 'SCRIPT') 129 | ) 130 | # execute_async_script or execute_script were called (but not both) 131 | script_content = mock_resource_string.return_value.decode() 132 | if async: 133 | mock_execute_async_script.assert_called_once_with(script_content, 134 | mock_arg) 135 | self.assertEqual(len(mock_execute_script.mock_calls), 0) 136 | # the result is the one from execute_async_script() 137 | self.assertIs(result, mock_execute_async_script.return_value) 138 | else: 139 | mock_execute_script.assert_called_once_with(script_content, 140 | mock_arg) 141 | self.assertEqual(len(mock_execute_async_script.mock_calls), 0) 142 | # the result is the one from execute_script() 143 | self.assertIs(result, mock_execute_script.return_value) 144 | 145 | def test__execute_client_script_async(self): 146 | self.verify__execute_client_script_call(True) 147 | 148 | def test__execute_client_script_sync(self): 149 | self.verify__execute_client_script_call(False) 150 | 151 | def verify_function_executes_script_with(self, func_to_call, 152 | script_name, *script_args, 153 | **script_kwargs): 154 | with patch.object( 155 | self.instance, '_execute_client_script' 156 | ) as mock_execute_client_script: 157 | result = func_to_call() 158 | mock_execute_client_script.assert_called_once_with( 159 | script_name, 160 | *script_args, **script_kwargs 161 | ) 162 | self.assertIs(result, mock_execute_client_script.return_value) 163 | 164 | def test_wait_for_angular(self): 165 | self.verify_function_executes_script_with( 166 | self.instance.wait_for_angular, 167 | 'waitForAngular', self.mock_root_element, async=True 168 | ) 169 | 170 | def test_wait_for_angular_does_not_call_script_if_ignore_synchronization( 171 | self 172 | ): 173 | """wait_for_angular() must not call the waitForAngular script, if 174 | ignore_synchronization is set to True.""" 175 | self.instance.ignore_synchronization = True 176 | 177 | with patch.object( 178 | self.instance, '_execute_client_script' 179 | ) as mock_execute_client_script: 180 | 181 | self.instance.wait_for_angular() 182 | 183 | self.assertEqual(mock_execute_client_script.call_count, 0) 184 | 185 | def test__test_for_angular(self): 186 | self.instance._test_timeout = 5000 187 | self.verify_function_executes_script_with( 188 | self.instance._test_for_angular, 189 | 'testForAngular', self.instance._test_timeout / 1000 190 | ) 191 | 192 | def test__location_equals(self): 193 | with patch.object( 194 | self.instance, 'execute_script', create=True 195 | ) as mock_execute_script: 196 | mock_location = MagicMock() 197 | mock_execute_script.return_value = MagicMock(__eq__=MagicMock()) 198 | result = self.instance._location_equals(mock_location) 199 | mock_execute_script.assert_called_once_with( 200 | 'return window.location.href' 201 | ) 202 | script_result = mock_execute_script.return_value 203 | script_result.__eq__.assert_called_once_with(mock_location) 204 | self.assertIs(result, script_result.__eq__.return_value) 205 | 206 | # The following tests test some properties that use the wait_for_angular 207 | # decorator and fetch the property from the super class. 208 | def verify_super_property_called_with_wait(self, prop_name): 209 | """ 210 | Verifies that accessing the given property calls the equally 211 | named property on the super class. 212 | """ 213 | with patch( 214 | super_str 215 | ) as mock_super, patch.object( 216 | self.instance, 'wait_for_angular' 217 | ) as mock_wait_for_angular: 218 | # setup the mocked property 219 | mock_prop = PropertyMock(name='super.{}'.format(prop_name)) 220 | setattr(type(mock_super.return_value), prop_name, mock_prop) 221 | 222 | result = getattr(self.instance, prop_name) 223 | 224 | mock_wait_for_angular.assert_called_once_with() 225 | mock_super.assert_called_once_with(WebDriverMixin, self.instance) 226 | mock_prop.assert_called_once_with() 227 | self.assertIs(result, mock_prop.return_value) 228 | 229 | def test_current_url(self): 230 | self.verify_super_property_called_with_wait('current_url') 231 | 232 | def test_page_source(self): 233 | self.verify_super_property_called_with_wait('page_source') 234 | 235 | def test_title(self): 236 | self.verify_super_property_called_with_wait('title') 237 | 238 | # Tests for methods that delegate to the super method 239 | def verify_super_method_called_with_wait(self, method_name): 240 | """ 241 | Verifies that calling the given method calls the equally 242 | named method on the super class. 243 | """ 244 | mock_args = [MagicMock(), MagicMock()] 245 | with patch( 246 | super_str 247 | ) as mock_super, patch.object( 248 | self.instance, 'wait_for_angular' 249 | ) as mock_wait_for_angular: 250 | mock_super_method = getattr(mock_super.return_value, method_name) 251 | method = getattr(self.instance, method_name) 252 | result = method(*mock_args) 253 | 254 | mock_wait_for_angular.assert_called_once_with() 255 | mock_super.assert_called_once_with(WebDriverMixin, self.instance) 256 | mock_super_method.assert_called_once_with(*mock_args) 257 | self.assertIs(result, mock_super_method.return_value) 258 | 259 | def test_find_element(self): 260 | self.verify_super_method_called_with_wait('find_element') 261 | 262 | def test_find_elements(self): 263 | self.verify_super_method_called_with_wait('find_elements') 264 | 265 | # tests for other methods 266 | def test_find_elements_by_binding(self): 267 | mock_descriptor = MagicMock() 268 | mock_using = MagicMock() 269 | with patch.multiple( 270 | self.instance, wait_for_angular=DEFAULT, 271 | _execute_client_script=DEFAULT 272 | ) as mock_methods: 273 | result = self.instance.find_elements_by_binding(mock_descriptor, 274 | mock_using) 275 | mock_methods['wait_for_angular'].assert_called_once_with() 276 | mock_methods['_execute_client_script'].assert_called_once_with( 277 | 'findBindings', mock_descriptor, False, mock_using, async=False 278 | ) 279 | self.assertIs(result, 280 | mock_methods['_execute_client_script'].return_value) 281 | 282 | def test_find_element_by_binding_no_element(self): 283 | mock_descriptor = MagicMock() 284 | mock_using = MagicMock() 285 | with patch.object( 286 | self.instance, 'find_elements_by_binding' 287 | ) as mock_find_elements_by_binding: 288 | mock_find_elements_by_binding.return_value = [] 289 | with self.assertRaises(NoSuchElementException): 290 | self.instance.find_element_by_binding(mock_descriptor, 291 | mock_using) 292 | mock_find_elements_by_binding.assert_called_once_with( 293 | mock_descriptor, using=mock_using 294 | ) 295 | 296 | def test_find_element_by_binding_with_element(self): 297 | mock_descriptor = MagicMock() 298 | mock_using = MagicMock() 299 | mock_element = MagicMock() 300 | with patch.object( 301 | self.instance, 'find_elements_by_binding' 302 | ) as mock_find_elements_by_binding: 303 | mock_find_elements_by_binding.return_value = [mock_element] 304 | result = self.instance.find_element_by_binding(mock_descriptor, 305 | mock_using) 306 | mock_find_elements_by_binding.assert_called_once_with( 307 | mock_descriptor, using=mock_using 308 | ) 309 | self.assertIs(result, mock_element) 310 | 311 | def test_get_with_angular(self): 312 | mock_url = MagicMock() 313 | with patch( 314 | super_str 315 | ) as mock_super, patch( 316 | 'pytractor.mixins.WebDriverWait' 317 | ) as mock_webdriverwait_class, patch.multiple( 318 | self.instance, _test_for_angular=DEFAULT, execute_script=DEFAULT, 319 | create=True # needed for execute_script 320 | ) as mock_methods: 321 | mock_test_for_angular = mock_methods['_test_for_angular'] 322 | # return a truthy value to indicate that angular was found 323 | mock_test_for_angular.return_value = (True,) 324 | mock_execute_script = mock_methods['execute_script'] 325 | 326 | self.instance.get(mock_url) 327 | mock_super.assert_called_once_with(WebDriverMixin, self.instance) 328 | mock_super.return_value.get.assert_called_once_with('about:blank') 329 | self.assertEqual(len(mock_execute_script.mock_calls), 2) 330 | mock_webdriverwait_class.assert_called_once_with(self.instance, 331 | 10) 332 | mock_webdriverwait_instance = mock_webdriverwait_class.return_value 333 | mock_webdriverwait_instance.until_not.assert_called_once_with( 334 | self.instance._location_equals, 'about:blank' 335 | ) 336 | mock_test_for_angular.assert_called_once_with() 337 | 338 | def test_get_without_angular(self): 339 | mock_url = MagicMock() 340 | with patch( 341 | super_str 342 | ) as mock_super, patch( 343 | 'pytractor.mixins.WebDriverWait' 344 | ) as mock_webdriverwait_class, patch.multiple( 345 | self.instance, _test_for_angular=DEFAULT, execute_script=DEFAULT, 346 | create=True # needed for execute_script 347 | ) as mock_methods: 348 | mock_test_for_angular = mock_methods['_test_for_angular'] 349 | # return a falsy value to indicate that angular was not found 350 | mock_test_for_angular.return_value = (False, 'ERROR') 351 | mock_execute_script = mock_methods['execute_script'] 352 | 353 | with self.assertRaises(AngularNotFoundException): 354 | self.instance.get(mock_url) 355 | mock_super.assert_called_once_with(WebDriverMixin, self.instance) 356 | mock_super.return_value.get.assert_called_once_with('about:blank') 357 | self.assertEqual(len(mock_execute_script.mock_calls), 1) 358 | mock_webdriverwait_class.assert_called_once_with(self.instance, 359 | 10) 360 | mock_webdriverwait_instance = mock_webdriverwait_class.return_value 361 | mock_webdriverwait_instance.until_not.assert_called_once_with( 362 | self.instance._location_equals, 'about:blank' 363 | ) 364 | mock_test_for_angular.assert_called_once_with() 365 | 366 | def test_get_does_not_test_for_angular_if_ignore_synchronization_is_true( 367 | self 368 | ): 369 | """Verify that get() does not call _test_for_angular if 370 | ignore_synchronization is set to True.""" 371 | mock_url = MagicMock() 372 | with patch( 373 | super_str 374 | ) as mock_super, patch( 375 | 'pytractor.mixins.WebDriverWait' 376 | ) as mock_webdriverwait_class, patch.multiple( 377 | self.instance, _test_for_angular=DEFAULT, execute_script=DEFAULT, 378 | create=True # needed for execute_script 379 | ) as mock_methods: 380 | mock_test_for_angular = mock_methods['_test_for_angular'] 381 | 382 | self.instance.ignore_synchronization = True 383 | self.instance.get(mock_url) 384 | 385 | self.assertEqual(mock_test_for_angular.call_count, 0) 386 | 387 | def test_refresh(self): 388 | with patch.multiple( 389 | self.instance, get=DEFAULT, execute_script=DEFAULT, 390 | create=True # needed for execute_script() 391 | ) as mock_methods: 392 | self.instance.refresh() 393 | mock_execute_script, mock_get = (mock_methods['execute_script'], 394 | mock_methods['get']) 395 | self.assertEqual(mock_execute_script.call_count, 1) 396 | mock_get.assert_called_once_with(mock_execute_script.return_value) 397 | 398 | def test_find_element_by_exact_binding_calls_find_elements(self): 399 | mock_descriptor = MagicMock() 400 | mock_using = MagicMock() 401 | mock_element = MagicMock() 402 | with patch.object( 403 | self.instance, 'find_elements_by_exact_binding', 404 | return_value=[mock_element] 405 | ) as mock_find_elements_by_exact_binding: 406 | result = self.instance.find_element_by_exact_binding( 407 | mock_descriptor, mock_using 408 | ) 409 | mock_find_elements_by_exact_binding.assert_called_once_with( 410 | mock_descriptor, using=mock_using 411 | ) 412 | self.assertIs(result, mock_element) 413 | 414 | def test_find_element_by_exact_binding_raises_error_if_nothing_found(self): 415 | mock_descriptor = MagicMock() 416 | mock_using = MagicMock() 417 | with patch.object( 418 | self.instance, 'find_elements_by_exact_binding', return_value=[] 419 | ) as mock_find_elements_by_exact_binding: 420 | with self.assertRaises(NoSuchElementException): 421 | self.instance.find_element_by_exact_binding( 422 | mock_descriptor, mock_using 423 | ) 424 | mock_find_elements_by_exact_binding.assert_called_once_with( 425 | mock_descriptor, using=mock_using 426 | ) 427 | 428 | def test_find_elements_by_exact_binding_calls_protractor_script(self): 429 | mock_descriptor = MagicMock() 430 | mock_using = MagicMock() 431 | 432 | with patch.multiple( 433 | self.instance, wait_for_angular=DEFAULT, 434 | _execute_client_script=DEFAULT 435 | ) as mock_methods: 436 | result = self.instance.find_elements_by_exact_binding( 437 | mock_descriptor, mock_using 438 | ) 439 | 440 | mock_methods['wait_for_angular'].assert_called_once_with() 441 | mock_methods['_execute_client_script'].assert_called_once_with( 442 | 'findBindings', mock_descriptor, True, mock_using, async=False 443 | ) 444 | self.assertIs(result, 445 | mock_methods['_execute_client_script'].return_value) 446 | 447 | def test_find_element_by_model_calls_find_elements(self): 448 | mock_descriptor = MagicMock() 449 | mock_using = MagicMock() 450 | with patch.object( 451 | self.instance, 'find_elements_by_model', return_value=[] 452 | ) as mock_find_elements_by_model: 453 | with self.assertRaises(NoSuchElementException): 454 | self.instance.find_element_by_model( 455 | mock_descriptor, mock_using 456 | ) 457 | mock_find_elements_by_model.assert_called_once_with( 458 | mock_descriptor, using=mock_using 459 | ) 460 | 461 | def test_find_element_by_model_raises_error_if_nothing_found(self): 462 | mock_descriptor = MagicMock() 463 | mock_using = MagicMock() 464 | with patch.object( 465 | self.instance, 'find_elements_by_model', return_value=[] 466 | ) as mock_find_elements_by_model: 467 | with self.assertRaises(NoSuchElementException): 468 | self.instance.find_element_by_model( 469 | mock_descriptor, mock_using 470 | ) 471 | mock_find_elements_by_model.assert_called_once_with( 472 | mock_descriptor, using=mock_using 473 | ) 474 | 475 | def test_find_elements_by_model_calls_protractor_script(self): 476 | mock_descriptor = MagicMock() 477 | mock_using = MagicMock() 478 | 479 | with patch.multiple( 480 | self.instance, wait_for_angular=DEFAULT, 481 | _execute_client_script=DEFAULT 482 | ) as mock_methods: 483 | result = self.instance.find_elements_by_model( 484 | mock_descriptor, mock_using 485 | ) 486 | 487 | mock_methods['wait_for_angular'].assert_called_once_with() 488 | mock_methods['_execute_client_script'].assert_called_once_with( 489 | 'findByModel', mock_descriptor, mock_using, async=False 490 | ) 491 | self.assertIs(result, 492 | mock_methods['_execute_client_script'].return_value) 493 | 494 | def test_find_elements_by_model_returns_empty_list_if_script_returns_none( 495 | self 496 | ): 497 | """Verify that find_elements_by_model() returns an empty list if 498 | the protractor script returns None. 499 | This is a test for issue #10 500 | """ 501 | with patch.object(self.instance, '_execute_client_script', 502 | return_value=None): 503 | result = self.instance.find_elements_by_model('does-not-exist') 504 | 505 | self.assertIsInstance(result, list) 506 | self.assertEqual(len(result), 0) 507 | 508 | def test_location_abs_url_calls_protractor_script(self): 509 | with patch.multiple( 510 | self.instance, wait_for_angular=DEFAULT, 511 | _execute_client_script=DEFAULT 512 | ) as mock_methods: 513 | result = self.instance.location_abs_url 514 | 515 | mock_methods['wait_for_angular'].assert_called_once_with() 516 | mock_methods['_execute_client_script'].assert_called_once_with( 517 | 'getLocationAbsUrl', self.instance._root_element, async=False 518 | ) 519 | self.assertIs(result, 520 | mock_methods['_execute_client_script'].return_value) 521 | 522 | def test_set_location_calls_protractor_script(self): 523 | url = 'http://a.new.locat.ion/' 524 | with patch.multiple( 525 | self.instance, wait_for_angular=DEFAULT, 526 | _execute_client_script=DEFAULT 527 | ) as mock_methods: 528 | result = self.instance.set_location(url) 529 | 530 | mock_methods['wait_for_angular'].assert_called_once_with() 531 | mock_methods['_execute_client_script'].assert_called_once_with( 532 | 'setLocation', self.instance._root_element, url, async=False 533 | ) 534 | self.assertIs(result, 535 | mock_methods['_execute_client_script'].return_value) 536 | -------------------------------------------------------------------------------- /src/pytractor/tests/unit/test_webdriver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | Unit tests for pytractor.webdriver 16 | """ 17 | from importlib import import_module 18 | import unittest 19 | 20 | from selenium import webdriver 21 | 22 | from pytractor.mixins import WebDriverMixin 23 | 24 | 25 | class WebDriverModuleTest(unittest.TestCase): 26 | # All known webdrivers 27 | WEBDRIVERS = ['Android', 'Chrome', 'Firefox', 'Ie', 'Opera', 'PhantomJS', 28 | 'Remote', 'Safari'] 29 | 30 | def setUp(self): 31 | self.module = import_module('pytractor.webdriver') 32 | 33 | def test_module_exports_all_webdrivers(self): 34 | for driver_name in self.WEBDRIVERS: 35 | self.assertTrue( 36 | hasattr(self.module, driver_name), 37 | '{} was not found in the module!'.format(driver_name) 38 | ) 39 | 40 | def test_exported_webdrivers_are_classes(self): 41 | for driver_name in self.WEBDRIVERS: 42 | klass = getattr(self.module, driver_name) 43 | self.assertIsInstance( 44 | klass, type, '{} is not a class!'.format(driver_name) 45 | ) 46 | 47 | def test_exported_webdrivers_are_subclasses_of_webdriver_and_mixin(self): 48 | for driver_name in self.WEBDRIVERS: 49 | selenium_driver = getattr(webdriver, driver_name) 50 | pytractor_driver = getattr(self.module, driver_name) 51 | self.assertTrue(issubclass(pytractor_driver, selenium_driver), 52 | 'pytractor.webdriver.{0} is not a subclass of ' 53 | 'selenium.webdriver.{0}'.format(driver_name)) 54 | self.assertTrue(issubclass(pytractor_driver, WebDriverMixin), 55 | 'pytractor.webdriver.{} is not a subclass of ' 56 | 'pytractor.mixins.WebDriverMixin' 57 | .format(driver_name)) 58 | -------------------------------------------------------------------------------- /src/pytractor/webdriver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Konrad Podloucky 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Selenium webdrivers with added angular.js awareness. 17 | """ 18 | 19 | from selenium import webdriver as selenium_webdriver 20 | from selenium.webdriver.remote.webdriver import WebDriver 21 | 22 | from .mixins import WebDriverMixin 23 | 24 | module_dict = globals() # pylint: disable=invalid-name 25 | __all__ = [] 26 | 27 | # build classes derived from selenium webdrivers and our WebDriverMixin 28 | for name in dir(selenium_webdriver): 29 | export = getattr(selenium_webdriver, name) 30 | if isinstance(export, type) and issubclass(export, WebDriver): 31 | module_dict[name] = type(name, (WebDriverMixin, export), {}) 32 | __all__.append(name) 33 | 34 | __all__ = tuple(__all__) 35 | --------------------------------------------------------------------------------